From 957c774b622d06b29c4c59e48db59e2e7f2ae734 Mon Sep 17 00:00:00 2001
From: Tor Norbye <tnorbye@google.com>
Date: Fri, 26 Jan 2018 17:12:10 -0800
Subject: [PATCH] Initial version of Metalava.

For full information, read README.md.

Test: ./gradlew test

Change-Id: Ibe7cf1162af36f50afa025c3af0ea9cdaa5135c7
---
 .gitignore                                    |    7 +
 .idea/codeStyles/codeStyleConfig.xml          |    5 +
 .idea/compiler.xml                            |    9 +
 .idea/copyright/aosp.xml                      |    6 +
 .idea/copyright/profiles_settings.xml         |    3 +
 .idea/gradle.xml                              |   17 +
 .idea/misc.xml                                |    6 +
 .idea/vcs.xml                                 |    6 +
 README.md                                     |  439 +++
 build.gradle                                  |   96 +
 gradle.properties                             |    3 +
 gradle/wrapper/gradle-wrapper.jar             |  Bin 0 -> 54708 bytes
 gradle/wrapper/gradle-wrapper.properties      |    6 +
 gradlew                                       |  172 +
 gradlew.bat                                   |   84 +
 manual/android/accounts/annotations.xml       |  159 +
 manual/android/content/annotations.xml        |   14 +
 manual/android/location/annotations.xml       |   15 +
 manual/android/provider/annotations.xml       |   89 +
 .../support/design/widget/annotations.xml     |    9 +
 manual/android/text/annotations.xml           |   14 +
 .../tools/lint/annotations/SdkUtils2.java     |  107 +
 .../lint/checks/infrastructure/ClassName.kt   |  171 +
 .../tools/metalava/AnnotationStatistics.kt    |  429 +++
 .../tools/metalava/AnnotationsMerger.kt       |  734 +++++
 .../com/android/tools/metalava/ApiAnalyzer.kt | 1085 +++++++
 .../tools/metalava/ComparisonVisitor.kt       |  300 ++
 .../android/tools/metalava/Compatibility.kt   |  162 +
 .../tools/metalava/CompatibilityCheck.kt      |  174 ++
 .../android/tools/metalava/DexApiWriter.kt    |   73 +
 .../com/android/tools/metalava/DocAnalyzer.kt |  521 ++++
 .../java/com/android/tools/metalava/Driver.kt |  686 ++++
 .../tools/metalava/ExtractAnnotations.kt      |   51 +
 .../tools/metalava/NullnessMigration.kt       |  170 +
 .../com/android/tools/metalava/Options.kt     | 1116 +++++++
 .../android/tools/metalava/PackageFilter.kt   |   26 +
 .../android/tools/metalava/ProguardWriter.kt  |  134 +
 .../com/android/tools/metalava/Reporter.kt    |  279 ++
 .../android/tools/metalava/SignatureWriter.kt |  289 ++
 .../com/android/tools/metalava/StubWriter.kt  |  516 +++
 .../com/android/tools/metalava/Terminal.kt    |   95 +
 .../metalava/apilevels/AndroidJarReader.java  |  166 +
 .../android/tools/metalava/apilevels/Api.java |   86 +
 .../tools/metalava/apilevels/ApiClass.java    |  205 ++
 .../tools/metalava/apilevels/ApiElement.java  |  229 ++
 .../metalava/apilevels/ApiGenerator.java      |  212 ++
 .../tools/metalava/doclava1/ApiFile.java      |  877 ++++++
 .../tools/metalava/doclava1/ApiInfo.kt        |  264 ++
 .../metalava/doclava1/ApiParseException.java  |   49 +
 .../tools/metalava/doclava1/ApiPredicate.kt   |  103 +
 .../metalava/doclava1/ElidingPredicate.kt     |   31 +
 .../tools/metalava/doclava1/Errors.java       |  236 ++
 .../metalava/doclava1/FilterPredicate.kt      |   35 +
 .../metalava/doclava1/SourcePositionInfo.java |   66 +
 .../tools/metalava/model/AnnotationItem.kt    |  456 +++
 .../android/tools/metalava/model/ClassItem.kt |  677 ++++
 .../android/tools/metalava/model/Codebase.kt  |  178 ++
 .../tools/metalava/model/CompilationUnit.kt   |   35 +
 .../tools/metalava/model/ConstructorItem.kt   |   30 +
 .../android/tools/metalava/model/FieldItem.kt |  304 ++
 .../com/android/tools/metalava/model/Item.kt  |  223 ++
 .../tools/metalava/model/MemberItem.kt        |   30 +
 .../tools/metalava/model/MethodItem.kt        |  383 +++
 .../tools/metalava/model/ModifierList.kt      |  294 ++
 .../metalava/model/MutableModifierList.kt     |   42 +
 .../tools/metalava/model/PackageItem.kt       |  111 +
 .../tools/metalava/model/PackageList.kt       |   44 +
 .../tools/metalava/model/ParameterItem.kt     |   80 +
 .../android/tools/metalava/model/TypeItem.kt  |  266 ++
 .../tools/metalava/model/psi/ClassType.kt     |   37 +
 .../tools/metalava/model/psi/Javadoc.kt       |  219 ++
 .../metalava/model/psi/PsiAnnotationItem.kt   |  381 +++
 .../metalava/model/psi/PsiBasedCodebase.kt    |  974 ++++++
 .../tools/metalava/model/psi/PsiClassItem.kt  |  764 +++++
 .../metalava/model/psi/PsiConstructorItem.kt  |  249 ++
 .../tools/metalava/model/psi/PsiFieldItem.kt  |  137 +
 .../tools/metalava/model/psi/PsiItem.kt       |  340 ++
 .../tools/metalava/model/psi/PsiMethodItem.kt |  294 ++
 .../metalava/model/psi/PsiModifierItem.kt     |  298 ++
 .../metalava/model/psi/PsiPackageItem.kt      |  163 +
 .../metalava/model/psi/PsiParameterItem.kt    |  123 +
 .../tools/metalava/model/psi/PsiTypeItem.kt   |  756 +++++
 .../model/text/TextBackedAnnotationItem.kt    |   59 +
 .../metalava/model/text/TextClassItem.kt      |  254 ++
 .../model/text/TextConstructorItem.kt         |   49 +
 .../metalava/model/text/TextFieldItem.kt      |   79 +
 .../tools/metalava/model/text/TextItem.kt     |   47 +
 .../metalava/model/text/TextMemberItem.kt     |   35 +
 .../metalava/model/text/TextMethodItem.kt     |  151 +
 .../metalava/model/text/TextModifiers.kt      |  167 +
 .../metalava/model/text/TextPackageItem.kt    |   59 +
 .../metalava/model/text/TextParameterItem.kt  |   62 +
 .../tools/metalava/model/text/TextTypeItem.kt |  177 ++
 .../metalava/model/visitors/ApiVisitor.kt     |   99 +
 .../metalava/model/visitors/ItemVisitor.kt    |   78 +
 .../model/visitors/PredicateVisitor.kt        |   40 +
 .../metalava/model/visitors/TypeVisitor.kt    |   26 +
 .../model/visitors/VisibleItemVisitor.kt      |   39 +
 .../metalava/AnnotationStatisticsTest.kt      |  163 +
 .../tools/metalava/AnnotationsMergerTest.kt   |  127 +
 .../com/android/tools/metalava/ApiFileTest.kt | 2091 +++++++++++++
 .../android/tools/metalava/ApiFromTextTest.kt |  280 ++
 .../tools/metalava/CompatibilityCheckTest.kt  |  377 +++
 .../android/tools/metalava/DocAnalyzerTest.kt | 1030 ++++++
 .../com/android/tools/metalava/DriverTest.kt  | 1160 +++++++
 .../tools/metalava/ExtractAnnotationsTest.kt  |  432 +++
 .../android/tools/metalava/KeepFileTest.kt    |   84 +
 .../tools/metalava/NullnessMigrationTest.kt   |  291 ++
 .../com/android/tools/metalava/OptionsTest.kt |  207 ++
 .../tools/metalava/PackageFilterTest.kt       |   32 +
 .../tools/metalava/ShowAnnotationTest.kt      |  127 +
 .../com/android/tools/metalava/StubsTest.kt   | 2776 +++++++++++++++++
 .../tools/metalava/SystemServiceCheckTest.kt  |  365 +++
 .../metalava/apilevels/ApiGeneratorTest.kt    |   81 +
 .../model/TextBackedAnnotationItemTest.kt     |   93 +
 .../tools/metalava/model/TypeItemTest.kt      |   35 +
 .../metalava/model/text/TextTypeItemTest.kt   |   54 +
 stub-annotations/build.gradle                 |    4 +
 .../android/support/annotation/Migrate.java   |   29 +
 .../support/annotation/NewlyNonNull.java      |   35 +
 .../support/annotation/NewlyNullable.java     |   35 +
 .../support/annotation/RecentlyNonNull.java   |   35 +
 .../support/annotation/RecentlyNullable.java  |   35 +
 123 files changed, 30123 insertions(+)
 create mode 100644 .gitignore
 create mode 100644 .idea/codeStyles/codeStyleConfig.xml
 create mode 100644 .idea/compiler.xml
 create mode 100644 .idea/copyright/aosp.xml
 create mode 100644 .idea/copyright/profiles_settings.xml
 create mode 100644 .idea/gradle.xml
 create mode 100644 .idea/misc.xml
 create mode 100644 .idea/vcs.xml
 create mode 100644 README.md
 create mode 100644 build.gradle
 create mode 100644 gradle.properties
 create mode 100644 gradle/wrapper/gradle-wrapper.jar
 create mode 100644 gradle/wrapper/gradle-wrapper.properties
 create mode 100755 gradlew
 create mode 100644 gradlew.bat
 create mode 100644 manual/android/accounts/annotations.xml
 create mode 100644 manual/android/content/annotations.xml
 create mode 100644 manual/android/location/annotations.xml
 create mode 100644 manual/android/provider/annotations.xml
 create mode 100644 manual/android/support/design/widget/annotations.xml
 create mode 100644 manual/android/text/annotations.xml
 create mode 100644 src/main/java/com/android/tools/lint/annotations/SdkUtils2.java
 create mode 100644 src/main/java/com/android/tools/lint/checks/infrastructure/ClassName.kt
 create mode 100644 src/main/java/com/android/tools/metalava/AnnotationStatistics.kt
 create mode 100644 src/main/java/com/android/tools/metalava/AnnotationsMerger.kt
 create mode 100644 src/main/java/com/android/tools/metalava/ApiAnalyzer.kt
 create mode 100644 src/main/java/com/android/tools/metalava/ComparisonVisitor.kt
 create mode 100644 src/main/java/com/android/tools/metalava/Compatibility.kt
 create mode 100644 src/main/java/com/android/tools/metalava/CompatibilityCheck.kt
 create mode 100644 src/main/java/com/android/tools/metalava/DexApiWriter.kt
 create mode 100644 src/main/java/com/android/tools/metalava/DocAnalyzer.kt
 create mode 100644 src/main/java/com/android/tools/metalava/Driver.kt
 create mode 100644 src/main/java/com/android/tools/metalava/ExtractAnnotations.kt
 create mode 100644 src/main/java/com/android/tools/metalava/NullnessMigration.kt
 create mode 100644 src/main/java/com/android/tools/metalava/Options.kt
 create mode 100644 src/main/java/com/android/tools/metalava/PackageFilter.kt
 create mode 100644 src/main/java/com/android/tools/metalava/ProguardWriter.kt
 create mode 100644 src/main/java/com/android/tools/metalava/Reporter.kt
 create mode 100644 src/main/java/com/android/tools/metalava/SignatureWriter.kt
 create mode 100644 src/main/java/com/android/tools/metalava/StubWriter.kt
 create mode 100644 src/main/java/com/android/tools/metalava/Terminal.kt
 create mode 100644 src/main/java/com/android/tools/metalava/apilevels/AndroidJarReader.java
 create mode 100644 src/main/java/com/android/tools/metalava/apilevels/Api.java
 create mode 100644 src/main/java/com/android/tools/metalava/apilevels/ApiClass.java
 create mode 100644 src/main/java/com/android/tools/metalava/apilevels/ApiElement.java
 create mode 100644 src/main/java/com/android/tools/metalava/apilevels/ApiGenerator.java
 create mode 100644 src/main/java/com/android/tools/metalava/doclava1/ApiFile.java
 create mode 100644 src/main/java/com/android/tools/metalava/doclava1/ApiInfo.kt
 create mode 100644 src/main/java/com/android/tools/metalava/doclava1/ApiParseException.java
 create mode 100644 src/main/java/com/android/tools/metalava/doclava1/ApiPredicate.kt
 create mode 100644 src/main/java/com/android/tools/metalava/doclava1/ElidingPredicate.kt
 create mode 100644 src/main/java/com/android/tools/metalava/doclava1/Errors.java
 create mode 100644 src/main/java/com/android/tools/metalava/doclava1/FilterPredicate.kt
 create mode 100644 src/main/java/com/android/tools/metalava/doclava1/SourcePositionInfo.java
 create mode 100644 src/main/java/com/android/tools/metalava/model/AnnotationItem.kt
 create mode 100644 src/main/java/com/android/tools/metalava/model/ClassItem.kt
 create mode 100644 src/main/java/com/android/tools/metalava/model/Codebase.kt
 create mode 100644 src/main/java/com/android/tools/metalava/model/CompilationUnit.kt
 create mode 100644 src/main/java/com/android/tools/metalava/model/ConstructorItem.kt
 create mode 100644 src/main/java/com/android/tools/metalava/model/FieldItem.kt
 create mode 100644 src/main/java/com/android/tools/metalava/model/Item.kt
 create mode 100644 src/main/java/com/android/tools/metalava/model/MemberItem.kt
 create mode 100644 src/main/java/com/android/tools/metalava/model/MethodItem.kt
 create mode 100644 src/main/java/com/android/tools/metalava/model/ModifierList.kt
 create mode 100644 src/main/java/com/android/tools/metalava/model/MutableModifierList.kt
 create mode 100644 src/main/java/com/android/tools/metalava/model/PackageItem.kt
 create mode 100644 src/main/java/com/android/tools/metalava/model/PackageList.kt
 create mode 100644 src/main/java/com/android/tools/metalava/model/ParameterItem.kt
 create mode 100644 src/main/java/com/android/tools/metalava/model/TypeItem.kt
 create mode 100644 src/main/java/com/android/tools/metalava/model/psi/ClassType.kt
 create mode 100644 src/main/java/com/android/tools/metalava/model/psi/Javadoc.kt
 create mode 100644 src/main/java/com/android/tools/metalava/model/psi/PsiAnnotationItem.kt
 create mode 100644 src/main/java/com/android/tools/metalava/model/psi/PsiBasedCodebase.kt
 create mode 100644 src/main/java/com/android/tools/metalava/model/psi/PsiClassItem.kt
 create mode 100644 src/main/java/com/android/tools/metalava/model/psi/PsiConstructorItem.kt
 create mode 100644 src/main/java/com/android/tools/metalava/model/psi/PsiFieldItem.kt
 create mode 100644 src/main/java/com/android/tools/metalava/model/psi/PsiItem.kt
 create mode 100644 src/main/java/com/android/tools/metalava/model/psi/PsiMethodItem.kt
 create mode 100644 src/main/java/com/android/tools/metalava/model/psi/PsiModifierItem.kt
 create mode 100644 src/main/java/com/android/tools/metalava/model/psi/PsiPackageItem.kt
 create mode 100644 src/main/java/com/android/tools/metalava/model/psi/PsiParameterItem.kt
 create mode 100644 src/main/java/com/android/tools/metalava/model/psi/PsiTypeItem.kt
 create mode 100644 src/main/java/com/android/tools/metalava/model/text/TextBackedAnnotationItem.kt
 create mode 100644 src/main/java/com/android/tools/metalava/model/text/TextClassItem.kt
 create mode 100644 src/main/java/com/android/tools/metalava/model/text/TextConstructorItem.kt
 create mode 100644 src/main/java/com/android/tools/metalava/model/text/TextFieldItem.kt
 create mode 100644 src/main/java/com/android/tools/metalava/model/text/TextItem.kt
 create mode 100644 src/main/java/com/android/tools/metalava/model/text/TextMemberItem.kt
 create mode 100644 src/main/java/com/android/tools/metalava/model/text/TextMethodItem.kt
 create mode 100644 src/main/java/com/android/tools/metalava/model/text/TextModifiers.kt
 create mode 100644 src/main/java/com/android/tools/metalava/model/text/TextPackageItem.kt
 create mode 100644 src/main/java/com/android/tools/metalava/model/text/TextParameterItem.kt
 create mode 100644 src/main/java/com/android/tools/metalava/model/text/TextTypeItem.kt
 create mode 100644 src/main/java/com/android/tools/metalava/model/visitors/ApiVisitor.kt
 create mode 100644 src/main/java/com/android/tools/metalava/model/visitors/ItemVisitor.kt
 create mode 100644 src/main/java/com/android/tools/metalava/model/visitors/PredicateVisitor.kt
 create mode 100644 src/main/java/com/android/tools/metalava/model/visitors/TypeVisitor.kt
 create mode 100644 src/main/java/com/android/tools/metalava/model/visitors/VisibleItemVisitor.kt
 create mode 100644 src/test/java/com/android/tools/metalava/AnnotationStatisticsTest.kt
 create mode 100644 src/test/java/com/android/tools/metalava/AnnotationsMergerTest.kt
 create mode 100644 src/test/java/com/android/tools/metalava/ApiFileTest.kt
 create mode 100644 src/test/java/com/android/tools/metalava/ApiFromTextTest.kt
 create mode 100644 src/test/java/com/android/tools/metalava/CompatibilityCheckTest.kt
 create mode 100644 src/test/java/com/android/tools/metalava/DocAnalyzerTest.kt
 create mode 100644 src/test/java/com/android/tools/metalava/DriverTest.kt
 create mode 100644 src/test/java/com/android/tools/metalava/ExtractAnnotationsTest.kt
 create mode 100644 src/test/java/com/android/tools/metalava/KeepFileTest.kt
 create mode 100644 src/test/java/com/android/tools/metalava/NullnessMigrationTest.kt
 create mode 100644 src/test/java/com/android/tools/metalava/OptionsTest.kt
 create mode 100644 src/test/java/com/android/tools/metalava/PackageFilterTest.kt
 create mode 100644 src/test/java/com/android/tools/metalava/ShowAnnotationTest.kt
 create mode 100644 src/test/java/com/android/tools/metalava/StubsTest.kt
 create mode 100644 src/test/java/com/android/tools/metalava/SystemServiceCheckTest.kt
 create mode 100644 src/test/java/com/android/tools/metalava/apilevels/ApiGeneratorTest.kt
 create mode 100644 src/test/java/com/android/tools/metalava/model/TextBackedAnnotationItemTest.kt
 create mode 100644 src/test/java/com/android/tools/metalava/model/TypeItemTest.kt
 create mode 100644 src/test/java/com/android/tools/metalava/model/text/TextTypeItemTest.kt
 create mode 100644 stub-annotations/build.gradle
 create mode 100644 stub-annotations/src/main/java/android/support/annotation/Migrate.java
 create mode 100644 stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
 create mode 100644 stub-annotations/src/main/java/android/support/annotation/NewlyNullable.java
 create mode 100644 stub-annotations/src/main/java/android/support/annotation/RecentlyNonNull.java
 create mode 100644 stub-annotations/src/main/java/android/support/annotation/RecentlyNullable.java

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..8ddfee5
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,7 @@
+*.iml
+.gradle
+/.idea/libraries
+/.idea/modules.xml
+/.idea/workspace.xml
+/out
+.DS_Store
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 0000000..a55e7a1
--- /dev/null
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+<component name="ProjectCodeStyleConfiguration">
+  <state>
+    <option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
+  </state>
+</component>
\ No newline at end of file
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 0000000..304a161
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="CompilerConfiguration">
+    <bytecodeTargetLevel>
+      <module name="metalava_main" target="1.8" />
+      <module name="metalava_test" target="1.8" />
+    </bytecodeTargetLevel>
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/copyright/aosp.xml b/.idea/copyright/aosp.xml
new file mode 100644
index 0000000..090509b
--- /dev/null
+++ b/.idea/copyright/aosp.xml
@@ -0,0 +1,6 @@
+<component name="CopyrightManager">
+  <copyright>
+    <option name="notice" value="Copyright (C) &amp;#36;today.year The Android Open Source Project&#10;&#10;Licensed under the Apache License, Version 2.0 (the &quot;License&quot;);&#10;you may not use this file except in compliance with the License.&#10;You may obtain a copy of the License at&#10;&#10;     http://www.apache.org/licenses/LICENSE-2.0&#10;&#10;Unless required by applicable law or agreed to in writing, software&#10;distributed under the License is distributed on an &quot;AS IS&quot; BASIS,&#10;WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.&#10;See the License for the specific language governing permissions and&#10;limitations under the License." />
+    <option name="myName" value="aosp" />
+  </copyright>
+</component>
\ No newline at end of file
diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml
new file mode 100644
index 0000000..39c0f13
--- /dev/null
+++ b/.idea/copyright/profiles_settings.xml
@@ -0,0 +1,3 @@
+<component name="CopyrightManager">
+  <settings default="aosp" />
+</component>
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
new file mode 100644
index 0000000..267d99c
--- /dev/null
+++ b/.idea/gradle.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="GradleSettings">
+    <option name="linkedExternalProjectsSettings">
+      <GradleProjectSettings>
+        <option name="distributionType" value="DEFAULT_WRAPPED" />
+        <option name="externalProjectPath" value="$PROJECT_DIR$" />
+        <option name="modules">
+          <set>
+            <option value="$PROJECT_DIR$" />
+          </set>
+        </option>
+        <option name="useAutoImport" value="true" />
+      </GradleProjectSettings>
+    </option>
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..84da703
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK">
+    <output url="file://$PROJECT_DIR$/classes" />
+  </component>
+</project>
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="$PROJECT_DIR$" vcs="Git" />
+  </component>
+</project>
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..5a46b2c
--- /dev/null
+++ b/README.md
@@ -0,0 +1,439 @@
+# Metalava
+
+(Also known as "doclava2", but deliberately not named doclava2 since crucially it
+does not generate docs; it's intended only for **meta**data extraction and generation.)
+
+Metalava is a metadata generator intended for the Android source tree, used for
+a number of purposes:
+
+* Allow extracting the API (into signature text files, into stub API files (which
+  in turn get compiled into android.jar, the Android SDK library)
+  and more importantly to hide code intended to be implementation only, driven
+  by javadoc comments like @hide, @$doconly, @removed, etc, as well as various
+  annotations.
+
+* Extracting source level annotations into external annotations file (such as
+  the typedef annotations, which cannot be stored in the SDK as .class level
+  annotations).
+
+* Diffing versions of the API and determining whether a newer version is compatible
+  with the older version.
+
+## Building and running
+
+To build:
+
+    $ ./gradlew
+
+This builds a binary distribution in `../../out/host/common/install/metalava/bin/metalava`.
+
+To run metalava:
+
+    $ ../../out/host/common/install/metalava/bin/metalava
+                    _        _
+     _ __ ___   ___| |_ __ _| | __ ___   ____ _
+    | '_ ` _ \ / _ \ __/ _` | |/ _` \ \ / / _` |
+    | | | | | |  __/ || (_| | | (_| |\ V / (_| |
+    |_| |_| |_|\___|\__\__,_|_|\__,_| \_/ \__,_|
+
+    metalava extracts metadata from source code to generate artifacts such as the
+    signature files, the SDK stub files, external annotations etc.
+
+    Usage: metalava <flags>
+
+    Flags:
+
+    --help                                This message.
+    --quiet                               Only include vital output
+    --verbose                             Include extra diagnostic output
+
+    ...
+(*output truncated*)
+
+Metalava has a new command line syntax, but it also understands the doclava1
+flags and translates them on the fly. Flags that are ignored are listed on
+the command line. If metalava is dropped into an Android framework build for
+example, you'll see something like this (unless running with --quiet) :
+
+    metalava: Ignoring unimplemented doclava1 flag -encoding (UTF-8 assumed)
+    metalava: Ignoring unimplemented doclava1 flag -source  (1.8 assumed)
+    metalava: Ignoring javadoc-related doclava1 flag -J-Xmx1600m
+    metalava: Ignoring javadoc-related doclava1 flag -J-XX:-OmitStackTraceInFastThrow
+    metalava: Ignoring javadoc-related doclava1 flag -XDignore.symbol.file
+    metalava: Ignoring javadoc-related doclava1 flag -doclet
+    metalava: Ignoring javadoc-related doclava1 flag -docletpath
+    metalava: Ignoring javadoc-related doclava1 flag -templatedir
+    metalava: Ignoring javadoc-related doclava1 flag -htmldir
+    ...
+
+## Features
+
+* Compatibility with doclava1: in compat mode, metalava spits out the same
+  signature files for the framework as doclava1.
+
+* Ability to read in an existing android.jar file instead of from source, which means
+  we can regenerate signature files etc for older versions according to new formats
+  (e.g. to fix past errors in doclava, such as annotation instance methods which were
+  accidentally not included.)
+
+* Ability to merge in data (annotations etc) from external sources, such as
+  IntelliJ external annotations data as well as signature files containing
+  annotations. This isn't just merged at export time, it's merged at codebase
+  load time such that it can be part of the API analysis.
+
+* Support for an updated signature file format:
+
+  * Address errors in the doclava1 format which for example was missing annotation
+    class instance methods
+
+  * Improve the signature format such that it for example labels enums "enum"
+    instead of "abstract class extends java.lang.Enum", annotations as "@interface"
+    instead of "abstract class extends java.lang.Annotation", sorts modifiers in
+    the canonical modifier order, using "extends" instead of "implements" for
+    the superclass of an interface, and many other similar tweaks outlined
+    in the `Compatibility` class. (Metalava also allows (and ignores) block
+    comments in the signature files.)
+
+  * Add support for writing (and reading) annotations into the signature
+    files. This is vital now that some of these annotations become part of
+    the API contract (in particular nullness contracts, as well as parameter
+    names and default values.)
+
+  * Support for a "compact" nullness format -- one based on Kotlin's syntax. Since
+    the goal is to have **all** API elements explicitly state their nullness
+    contract, the signature files would very quickly become bloated with
+    @NonNull and @Nullable annotations everywhere. So instead, the signature
+    format now uses a suffix of `?` for nullable, `!` for not yet annotated, and
+    nothing for non-null.
+
+    Instead of
+
+        method public java.lang.Double convert0(java.lang.Float);
+        method @Nullable public java.lang.Double convert1(@NonNull java.lang.Float);
+
+    we have
+
+        method public java.lang.Double! convert0(java.lang.Float!);
+        method public java.lang.Double? convert1(java.lang.Float);
+
+
+  * Other compactness improvements: Skip packages in some cases both for
+    export and reinsert during import. Specifically, drop "java.lang."
+    from package names such that you have
+
+        method public void onUpdate(int, String);
+
+    instead of
+
+        method public void onUpdate(int, java.lang.String);
+
+    Similarly, annotations (the ones considered part of the API; unknown
+    annotations are not included in signature files) use just the simple
+    name instead of the full package name, e.g. `@UiThread` instead of
+    `@android.annotation.UiThread`.
+
+  * Misc documentation handling; for example, it attempts to fix sentences
+    that javadoc will mistreat, such as sentences that "end" with "e.g. ".
+    It also looks for various common typos and fixes those; here's a sample
+    error message running metalava on master:
+    Enhancing docs:
+
+        frameworks/base/core/java/android/content/res/AssetManager.java:166: error: Replaced Kitkat with KitKat in documentation for Method android.content.res.AssetManager.getLocales() [Typo]
+        frameworks/base/core/java/android/print/PrinterCapabilitiesInfo.java:122: error: Replaced Kitkat with KitKat in documentation for Method android.print.PrinterCapabilitiesInfo.Builder.setColorModes(int, int) [Typo]
+
+* Built-in support for injecting new annotations for use by the Kotlin compiler,
+  not just nullness annotations found in the source code and annotations merged
+  in from external sources, but also inferring whether nullness annotations
+  have recently changed and if so marking them as @Migrate (which lets the
+  Kotlin compiler treat errors in the user code as warnings instead of errors.)
+
+* Support for generating documentation into the stubs files (so we can run javadoc or
+  [Dokka](https://github.com/Kotlin/dokka) on the stubs files instead of the source
+  code). This means that the documentation tool itself does not need to be able to
+  figure out which parts of the source code is included in the API and which one is
+  implementation; it is simply handed the filtered API stub sources that include
+  documentation.
+
+* Support for parsing Kotlin files. API files can now be implemented in Kotlin
+  as well and metalava will parse and extract API information from them just
+  as is done for Java files.
+
+* Like doclava1, metalava can diff two APIs and warn about API compatibility
+  problems such as removing API elements. Metalava adds new warnings around
+  nullness, such as attempting to change a nullness contract incompatibly
+  (e.g. you can change a parameter from non null to nullable for final classes,
+  but not versa).  It also lets you diff directly on a source tree; it doesn't
+  require you to create two signature files to diff.
+
+* Consistent stubs: In doclava1, the code which iterated over the API and generated
+  the signature files and generated the stubs had diverged, so there was some
+  inconsistency. In metalava the stub files contain **exactly** the same signatures
+  as in the signature files.
+
+* Metalava can generate reports about nullness annotation coverage (which helps
+  target efforts since we plan to annotate the entire API). First, it can
+  generate a raw count:
+
+        Nullness Annotation Coverage Statistics:
+        1279 out of 46900 methods were annotated (2%)
+        2 out of 21683 fields were annotated (0%)
+        2770 out of 47492 parameters were annotated (5%)
+
+  More importantly, you can also point it to some existing compiled applications
+  (.class or .jar files) and it will then measure the annotation coverage of
+  the APIs used by those applications. This lets us target the most important
+  APIs that are currently used by a corpus of apps and target our annotation
+  efforts in a targeted way. For example, running the analysis on the current
+  version of framework, and pointing it to the
+  [Plaid](https://github.com/nickbutcher/plaid) app's compiled output with
+
+      ... --annotation-coverage-of ~/plaid/app/build/intermediates/classes/debug
+
+  This produces the following output:
+
+    324 methods and fields were missing nullness annotations out of 650 total API references.
+    API nullness coverage is 50%
+
+    |--------------------------------------------------------------|------------------|
+    | Qualified Class Name                                         |      Usage Count |
+    |--------------------------------------------------------------|-----------------:|
+    | android.os.Parcel                                            |              146 |
+    | android.view.View                                            |              119 |
+    | android.view.ViewPropertyAnimator                            |              114 |
+    | android.content.Intent                                       |              104 |
+    | android.graphics.Rect                                        |               79 |
+    | android.content.Context                                      |               61 |
+    | android.widget.TextView                                      |               53 |
+    | android.transition.TransitionValues                          |               49 |
+    | android.animation.Animator                                   |               34 |
+    | android.app.ActivityOptions                                  |               34 |
+    | android.view.LayoutInflater                                  |               31 |
+    | android.app.Activity                                         |               28 |
+    | android.content.SharedPreferences                            |               26 |
+    | android.content.SharedPreferences.Editor                     |               26 |
+    | android.text.SpannableStringBuilder                          |               23 |
+    | android.view.ViewGroup.MarginLayoutParams                    |               21 |
+    | ... (99 more items                                           |                  |
+    |--------------------------------------------------------------|------------------|
+
+    Top referenced un-annotated members:
+
+    |--------------------------------------------------------------|------------------|
+    | Member                                                       |      Usage Count |
+    |--------------------------------------------------------------|-----------------:|
+    | Parcel.readString()                                          |               62 |
+    | Parcel.writeString(String)                                   |               62 |
+    | TextView.setText(CharSequence)                               |               34 |
+    | TransitionValues.values                                      |               28 |
+    | View.getContext()                                            |               28 |
+    | ViewPropertyAnimator.setDuration(long)                       |               26 |
+    | ViewPropertyAnimator.setInterpolator(android.animation.Ti... |               26 |
+    | LayoutInflater.inflate(int, android.view.ViewGroup, boole... |               23 |
+    | Rect.left                                                    |               22 |
+    | Rect.top                                                     |               22 |
+    | Intent.Intent(android.content.Context, Class<?>)             |               21 |
+    | Rect.bottom                                                  |               21 |
+    | TransitionValues.view                                        |               21 |
+    | VERSION.SDK_INT                                              |               18 |
+    | Context.getResources()                                       |               18 |
+    | EditText.getText()                                           |               18 |
+    | ... (309 more items                                          |                  |
+    |--------------------------------------------------------------|------------------|
+
+
+  From this it's clear that it would be useful to start annotating android.os.Parcel
+  and android.view.View for example where there are unannotated APIs that are
+  frequently used, at least by this app.
+
+* Built on top of a full, type-resolved AST. Doclava1 was integrated with javadoc,
+  which meant that most of the source tree was opaque. Therefore, as just one example,
+  the code which generated documentation for typedef constants had to require the
+  constants to all share a single prefix it could look for. However, in metalava,
+  annotation references are available at the AST level, so it can resolve references
+  and map them back to the original field references and include those directly.
+
+* Support for extracting annotations. Metalava can also generate the external annotation
+  files needed by Studio and lint in Gradle, which captures the typedefs (@IntDef and
+  @StringDef classes) in the source code. Prior to this this was generated manually
+  via the development/tools/extract code. This also merges in manually curated data;
+  some of this is in the manual/ folder in this project.
+
+* Support for extracting API levels (api-versions.xml). This was generated by separate
+  code (tools/base/misc/api-generator), invoked during the build. This functionality
+  is now rolled into metalava, which has one very important attribute: metalava
+  will use this information when recording API levels for API usage. (Prior to this,
+  this was based on signature file parsing in doclava, which sometimes generated
+  incorrect results. Metalava uses the android.jar files themselves to ensure that
+  it computes the exact available SDK data for each API level.)
+
+## Architecture & Implementation
+
+Metalava is implemented on top of IntelliJ parsing APIs (PSI and UAST). However,
+these are hidden behind a "model": an abstraction layer which only exposes high
+level concepts like packages, classes and inner classes, methods, fields, and
+modifier lists (including annotations).
+
+This is done for multiple reasons:
+
+(1) It allows us to have multiple "back-ends": for example, metalava can read
+    in a model not just from parsing source code, but from reading older SDK
+    android.jar files (e.g. backed by bytecode) or reading previous signature
+    files.  Reading in multiple versions of an API lets doclava perform "diffing",
+    such as warning if an API is changing in an incompatible way. It can also
+    generate signature files in the new format (including data that was missing
+    in older signature files, such as annotation methods) without having to
+    parse older source code which may no longer be easy to parse.
+
+(2) There's a lot of logic for deciding whether code found in the source tree
+    should be included in the API. With the model approach we can build up an
+    API and for example mark a subset of its methods as included. By having
+    a separate hierarchy we can easily perform this work once and pass around
+    our filtered model instead of passing around PsiClass and PsiMethod instances
+    and having to keep the filtered data separately and remembering to always
+    consult the filter, not the PSI elements directly.
+
+The basic API element class is "Item". (In doclava1 this was called a "DocInfo".)
+There are several sub interfaces of Item: PackageItem, ClassItem, MemberItem,
+MethodItem, FieldItem, ParameterItem, etc. And then there are several
+implementation hierarchies: One is PSI based, where you point metalava to a
+source tree or a .jar file, and it constructs Items built on top of PSI:
+PsiPackageItem, PsiClassItem, PsiMethodItem, etc. Another is textual, based
+on signature files: TextPackageItem, TextClassItem, and so on.
+
+The "Codebase" class captures a complete API snapshot (including classes
+that are hidden, which is why it's called a "Codebase" rather than an "API").
+
+There are methods to load codebases - from source folders, from a .jar file,
+from a signature file. That's how API diffing is performed: you load two
+codebases (from whatever source you want, typically a previous API signature
+file and the current set of source folders), and then you "diff" the two.
+
+There are several key helpers that help with the implementation, detailed next.
+
+### Visiting Items
+
+First, metalava provides an ItemVisitor. This lets you visit the API easily.
+For example, here's how you can visit every class:
+
+    coebase.accept(object : ItemVisitor() {
+        override fun visitClass(cls: ClassItem) {
+            // code operating on the class here
+        }
+    })
+
+Similarly you can visit all items (regardless of type) by overriding
+`visitItem`, or to specifically visit methods, fields and so on
+overriding `visitPackage`, `visitClass`, `visitMethod`, etc.
+
+There is also an `ApiVisitor`. This is a subclass of the `ItemVisitor`,
+but which limits itself to visiting code elements that are part of the
+API.
+
+This is how for example the SignatureWriter and the StubWriter are both
+implemented: they simply extend `ApiVisitor`, which means they'll
+only export the API items in the codebase, and then in each relevant
+method they emit the signature or stub data:
+
+    class SignatureWriter(
+            private val writer: PrintWriter,
+            private val generateDefaultConstructors: Boolean,
+            private val filter: (Item) -> Boolean) : ApiVisitor(
+            visitConstructorsAsMethods = false) {
+
+    ....
+
+    override fun visitConstructor(constructor: ConstructorItem) {
+        writer.print("    ctor ")
+        writeModifiers(constructor)
+        writer.print(constructor.containingClass().fullName())
+        writeParameterList(constructor)
+        writeThrowsList(constructor)
+        writer.print(";\n")
+    }
+
+    ....
+
+### Visiting Types
+
+There is a `TypeVisitor` similar to `ItemVisitor` which you can use
+to visit all types in the codebase.
+
+When computing the API, all types that are included in the API should be
+included (e.g. if `List<Foo>` is part of the API then `Foo` must be too).
+This is easy to do with the `TypeVisitor`.
+
+### Diffing Codebases
+
+Another visitor which helps with implementation is the ComparisonVisitor:
+
+    open class ComparisonVisitor {
+        open fun compare(old: Item, new: Item) {}
+        open fun added(item: Item) {}
+        open fun removed(item: Item) {}
+
+        open fun compare(old: PackageItem, new: PackageItem) { }
+        open fun compare(old: ClassItem, new: ClassItem) { }
+        open fun compare(old: MethodItem, new: MethodItem) { }
+        open fun compare(old: FieldItem, new: FieldItem) { }
+        open fun compare(old: ParameterItem, new: ParameterItem) { }
+
+        open fun added(item: PackageItem) { }
+        open fun added(item: ClassItem) { }
+        open fun added(item: MethodItem) { }
+        open fun added(item: FieldItem) { }
+        open fun added(item: ParameterItem) { }
+
+        open fun removed(item: PackageItem) { }
+        open fun removed(item: ClassItem) { }
+        open fun removed(item: MethodItem) { }
+        open fun removed(item: FieldItem) { }
+        open fun removed(item: ParameterItem) { }
+    }
+
+This makes it easy to perform API comparison operations.
+
+For example, metalava has a feature to mark "newly annotated" nullness annotations
+as migrated. To do this, it just extends `ComparisonVisitor`, overrides the
+`compare(old: Item, new: Item)` method, and checks whether the old item
+has no nullness annotations and the new one does, and if so, also marks
+the new annotations as @Migrate.
+
+Similarly, the API Check can simply override
+
+    open fun removed(item: Item) {
+        reporter.report(error, item, "Removing ${Item.describe(item)} is not allowed")
+    }
+
+to flag all API elements that have been removed as invalid (since you cannot
+remove API.)
+
+### Documentation Generation
+
+As mentioned above, metalava generates documentation directly into the stubs
+files, which can then be processed by Dokka and Javadoc to generate the
+same docs as before.
+
+Doclava1 was integrated with javadoc directly, so the way it generated
+metadata docs (such as documenting permissions, ranges and typedefs from
+annotations) was to insert auxiliary tags (`@range`, `@permission`, etc) and
+then this would get converted into English docs later via `macros_override.cs`.
+
+This it not how metalava does it; it generates the English documentation
+directly. This was not just convenient for the implementation (since metalava
+does not use javadoc data structures to pass maps like the arguments for
+the typedef macro), but should also help Dokka -- and arguably the Kotlin
+code which generates the documentation is easier to reason about and to
+update when it's handling loop conditionals. (As a result I for example
+improved some of the grammar, e.g. when it's listing a number of possible
+constants the conjunction is usually "or", but if it's a flag, the sentence
+begins with "a combination of " and then the conjunction at the end should
+be "and").
+
+## Current Status
+
+Some things are still missing before this tool can be integrated:
+
+- doclava1 had various error checking, and many of these have not been included yet
+
+- the code needs cleanup, and some performance optimizations (it's about 3x
+  slower than doclava1)
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..4aeeeb2
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,96 @@
+buildscript {
+    ext.gradle_version = '3.1.0-alpha09'
+    ext.studio_version = '26.1.0-alpha09'
+    ext.kotlin_version = '1.2.21'
+    repositories {
+        google()
+        jcenter()
+    }
+    dependencies {
+        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+        classpath "com.android.tools.build:gradle:$gradle_version"
+    }
+}
+
+apply plugin: 'application'
+apply plugin: 'java'
+apply plugin: 'kotlin'
+apply plugin: 'maven'
+
+group = 'com.android'
+version = '0.9.0'
+
+mainClassName = "com.android.tools.metalava.Driver"
+applicationDefaultJvmArgs = ["-ea", "-Xms2g", "-Xmx4g"]
+sourceCompatibility = 1.8
+
+compileKotlin {
+    sourceCompatibility = JavaVersion.VERSION_1_8
+    targetCompatibility = JavaVersion.VERSION_1_8
+
+    kotlinOptions {
+        jvmTarget = "1.8"
+        apiVersion = "1.2"
+        languageVersion = "1.2"
+    }
+}
+
+repositories {
+    google()
+    jcenter()
+}
+
+dependencies {
+    implementation "com.android.tools.external.org-jetbrains:uast:$studio_version"
+    implementation "com.android.tools.external.com-intellij:intellij-core:$studio_version"
+    implementation "com.android.tools.lint:lint-api:$studio_version"
+    implementation "com.android.tools.lint:lint-checks:$studio_version"
+    implementation "com.android.tools.lint:lint-gradle:$studio_version"
+    implementation "com.android.tools.lint:lint:$studio_version"
+    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
+    testImplementation 'junit:junit:4.11'
+    testImplementation "com.android.tools.lint:lint-tests:$studio_version"
+}
+
+// shadow jar: Includes all dependencies
+buildscript {
+    repositories {
+        jcenter()
+    }
+    dependencies {
+        classpath 'com.github.jengelman.gradle.plugins:shadow:2.0.2'
+    }
+}
+apply plugin: 'com.github.johnrengelman.shadow'
+shadowJar {
+   baseName = "metalava-$version-full-SNAPSHOT"
+   classifier = null
+   version = null
+   zip64 = true
+}
+
+defaultTasks 'installDist'
+
+/*
+ * With the build server you are given two env variables:
+ * 1. The OUT_DIR is a temporary directory you can use to put things during the build.
+ * 2. The DIST_DIR is where you want to save things from the build.
+ *
+ * The build server will copy the contents of DIST_DIR to somewhere and make it available.
+ */
+if (System.env.DIST_DIR != null && System.env.OUT_DIR != null) {
+    buildDir = file("${System.env.OUT_DIR}/host/common/metalava").getCanonicalFile()
+    ext.distDir = file(System.env.DIST_DIR).getCanonicalFile()
+    ext.distsDir = ext.distDir
+
+    // The distDir is conveniently named after the build ID.
+    version = "${version}.${ext.distDir.name}"
+} else {
+    buildDir = file('../../out/host/common')
+    ext.distDir = file('../../out/dist')
+    ext.distsDir = ext.distDir
+
+    // Local builds are not public release candidates.
+    version = "${version}-SNAPSHOT"
+}
+
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..8998b2f
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,3 @@
+org.gradle.jvmargs=-Xmx4096m -XX:MaxPermSize=1024m
+org.gradle.daemon=true
+kotlin.incremental.usePreciseJavaTracking=true
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000000000000000000000000000000000000..7a3265ee94c0ab25cf079ac8ccdf87f41d455d42
GIT binary patch
literal 54708
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
Zc_R%5SO@)xU1S1!2k^J>_x$+l{{cH8$W#CT

literal 0
HcmV?d00001

diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..efc27cb
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Sat Jan 13 09:12:34 PST 2018
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.4.1-all.zip
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000..cccdd3d
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+##  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=""
+
+# 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, switch paths to Windows format before running java
+if $cygwin ; 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=$((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"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+  cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..e95643d
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,84 @@
+@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=
+
+@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/manual/android/accounts/annotations.xml b/manual/android/accounts/annotations.xml
new file mode 100644
index 0000000..f47e761
--- /dev/null
+++ b/manual/android/accounts/annotations.xml
@@ -0,0 +1,159 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+    <item name="android.accounts.AccountManager android.accounts.AccountManagerFuture&lt;android.accounts.Account&gt; renameAccount(android.accounts.Account, java.lang.String, android.accounts.AccountManagerCallback&lt;android.accounts.Account&gt;, android.os.Handler)">
+        <annotation name="android.support.annotation.RequiresPermission">
+            <val name="value" val="&quot;android.permission.AUTHENTICATE_ACCOUNTS&quot;" />
+            <val name="apis" val="&quot;..22&quot;" />
+        </annotation>
+    </item>
+    <item name="android.accounts.AccountManager android.accounts.AccountManagerFuture&lt;android.os.Bundle&gt; addAccount(java.lang.String, java.lang.String, java.lang.String[], android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback&lt;android.os.Bundle&gt;, android.os.Handler)">
+        <annotation name="android.support.annotation.RequiresPermission">
+            <val name="value" val="&quot;android.permission.MANAGE_ACCOUNTS&quot;" />
+            <val name="apis" val="&quot;..22&quot;" />
+        </annotation>
+    </item>
+    <item name="android.accounts.AccountManager android.accounts.AccountManagerFuture&lt;android.os.Bundle&gt; confirmCredentials(android.accounts.Account, android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback&lt;android.os.Bundle&gt;, android.os.Handler)">
+        <annotation name="android.support.annotation.RequiresPermission">
+            <val name="value" val="&quot;android.permission.MANAGE_ACCOUNTS&quot;" />
+            <val name="apis" val="&quot;..22&quot;" />
+        </annotation>
+    </item>
+    <item name="android.accounts.AccountManager android.accounts.AccountManagerFuture&lt;android.os.Bundle&gt; editProperties(java.lang.String, android.app.Activity, android.accounts.AccountManagerCallback&lt;android.os.Bundle&gt;, android.os.Handler)">
+        <annotation name="android.support.annotation.RequiresPermission">
+            <val name="value" val="&quot;android.permission.MANAGE_ACCOUNTS&quot;" />
+            <val name="apis" val="&quot;..22&quot;" />
+        </annotation>
+    </item>
+    <item name="android.accounts.AccountManager android.accounts.AccountManagerFuture&lt;android.os.Bundle&gt; getAuthToken(android.accounts.Account, java.lang.String, android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback&lt;android.os.Bundle&gt;, android.os.Handler)">
+        <annotation name="android.support.annotation.RequiresPermission">
+            <val name="value" val="&quot;android.permission.USE_CREDENTIALS&quot;" />
+            <val name="apis" val="&quot;..22&quot;" />
+        </annotation>
+    </item>
+    <item name="android.accounts.AccountManager android.accounts.AccountManagerFuture&lt;android.os.Bundle&gt; getAuthToken(android.accounts.Account, java.lang.String, android.os.Bundle, boolean, android.accounts.AccountManagerCallback&lt;android.os.Bundle&gt;, android.os.Handler)">
+        <annotation name="android.support.annotation.RequiresPermission">
+            <val name="value" val="&quot;android.permission.USE_CREDENTIALS&quot;" />
+            <val name="apis" val="&quot;..22&quot;" />
+        </annotation>
+    </item>
+    <item name="android.accounts.AccountManager android.accounts.AccountManagerFuture&lt;android.os.Bundle&gt; getAuthToken(android.accounts.Account, java.lang.String, boolean, android.accounts.AccountManagerCallback&lt;android.os.Bundle&gt;, android.os.Handler)">
+        <annotation name="android.support.annotation.RequiresPermission">
+            <val name="value" val="&quot;android.permission.USE_CREDENTIALS&quot;" />
+            <val name="apis" val="&quot;..22&quot;" />
+        </annotation>
+    </item>
+    <item name="android.accounts.AccountManager android.accounts.AccountManagerFuture&lt;android.os.Bundle&gt; getAuthTokenByFeatures(java.lang.String, java.lang.String, java.lang.String[], android.app.Activity, android.os.Bundle, android.os.Bundle, android.accounts.AccountManagerCallback&lt;android.os.Bundle&gt;, android.os.Handler)">
+        <annotation name="android.support.annotation.RequiresPermission">
+            <val name="value" val="&quot;android.permission.MANAGE_ACCOUNTS&quot;" />
+            <val name="apis" val="&quot;..22&quot;" />
+        </annotation>
+    </item>
+    <item name="android.accounts.AccountManager android.accounts.AccountManagerFuture&lt;android.os.Bundle&gt; removeAccount(android.accounts.Account, android.app.Activity, android.accounts.AccountManagerCallback&lt;android.os.Bundle&gt;, android.os.Handler)">
+        <annotation name="android.support.annotation.RequiresPermission">
+            <val name="value" val="&quot;android.permission.MANAGE_ACCOUNTS&quot;" />
+            <val name="apis" val="&quot;..22&quot;" />
+        </annotation>
+    </item>
+    <item name="android.accounts.AccountManager android.accounts.AccountManagerFuture&lt;android.os.Bundle&gt; updateCredentials(android.accounts.Account, java.lang.String, android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback&lt;android.os.Bundle&gt;, android.os.Handler)">
+        <annotation name="android.support.annotation.RequiresPermission">
+            <val name="value" val="&quot;android.permission.MANAGE_ACCOUNTS&quot;" />
+            <val name="apis" val="&quot;..22&quot;" />
+        </annotation>
+    </item>
+    <item name="android.accounts.AccountManager android.accounts.AccountManagerFuture&lt;java.lang.Boolean&gt; removeAccount(android.accounts.Account, android.accounts.AccountManagerCallback&lt;java.lang.Boolean&gt;, android.os.Handler)">
+        <annotation name="android.support.annotation.RequiresPermission">
+            <val name="value" val="&quot;android.permission.MANAGE_ACCOUNTS&quot;" />
+            <val name="apis" val="&quot;..22&quot;" />
+        </annotation>
+    </item>
+    <item name="android.accounts.AccountManager boolean addAccountExplicitly(android.accounts.Account, java.lang.String, android.os.Bundle)">
+        <annotation name="android.support.annotation.RequiresPermission">
+            <val name="value" val="&quot;android.permission.AUTHENTICATE_ACCOUNTS&quot;" />
+            <val name="apis" val="&quot;..22&quot;" />
+        </annotation>
+    </item>
+    <item name="android.accounts.AccountManager boolean notifyAccountAuthenticated(android.accounts.Account)">
+        <annotation name="android.support.annotation.RequiresPermission">
+            <val name="value" val="&quot;android.permission.AUTHENTICATE_ACCOUNTS&quot;" />
+            <val name="apis" val="&quot;..22&quot;" />
+        </annotation>
+    </item>
+    <item name="android.accounts.AccountManager boolean removeAccountExplicitly(android.accounts.Account)">
+        <annotation name="android.support.annotation.RequiresPermission">
+            <val name="value" val="&quot;android.permission.AUTHENTICATE_ACCOUNTS&quot;" />
+            <val name="apis" val="&quot;..22&quot;" />
+        </annotation>
+    </item>
+    <item name="android.accounts.AccountManager java.lang.String blockingGetAuthToken(android.accounts.Account, java.lang.String, boolean)">
+        <annotation name="android.support.annotation.RequiresPermission">
+            <val name="value" val="&quot;android.permission.USE_CREDENTIALS&quot;" />
+            <val name="apis" val="&quot;..22&quot;" />
+        </annotation>
+    </item>
+    <item name="android.accounts.AccountManager java.lang.String getPassword(android.accounts.Account)">
+        <annotation name="android.support.annotation.RequiresPermission">
+            <val name="value" val="&quot;android.permission.AUTHENTICATE_ACCOUNTS&quot;" />
+            <val name="apis" val="&quot;..22&quot;" />
+        </annotation>
+    </item>
+    <item name="android.accounts.AccountManager java.lang.String getUserData(android.accounts.Account, java.lang.String)">
+        <annotation name="android.support.annotation.RequiresPermission">
+            <val name="value" val="&quot;android.permission.AUTHENTICATE_ACCOUNTS&quot;" />
+            <val name="apis" val="&quot;..22&quot;" />
+        </annotation>
+    </item>
+    <item name="android.accounts.AccountManager java.lang.String peekAuthToken(android.accounts.Account, java.lang.String)">
+        <annotation name="android.support.annotation.RequiresPermission">
+            <val name="value" val="&quot;android.permission.AUTHENTICATE_ACCOUNTS&quot;" />
+            <val name="apis" val="&quot;..22&quot;" />
+        </annotation>
+    </item>
+    <item name="android.accounts.AccountManager void addOnAccountsUpdatedListener(android.accounts.OnAccountsUpdateListener, android.os.Handler, boolean)">
+        <annotation name="android.support.annotation.RequiresPermission">
+            <val name="value" val="&quot;android.permission.GET_ACCOUNTS&quot;" />
+            <val name="apis" val="&quot;..22&quot;" />
+        </annotation>
+    </item>
+    <item name="android.accounts.AccountManager void clearPassword(android.accounts.Account)">
+        <annotation name="android.support.annotation.RequiresPermission">
+            <val name="value" val="&quot;android.permission.MANAGE_ACCOUNTS&quot;" />
+            <val name="apis" val="&quot;..22&quot;" />
+        </annotation>
+    </item>
+    <item name="android.accounts.AccountManager void invalidateAuthToken(java.lang.String, java.lang.String)">
+        <annotation name="android.support.annotation.RequiresPermission">
+            <val name="anyOf" val="{&quot;android.permission.MANAGE_ACCOUNTS&quot;, &quot;android.permission.USE_CREDENTIALS&quot;}" />
+            <val name="apis" val="&quot;..22&quot;" />
+        </annotation>
+    </item>
+    <item name="android.accounts.AccountManager void setAuthToken(android.accounts.Account, java.lang.String, java.lang.String)">
+        <annotation name="android.support.annotation.RequiresPermission">
+            <val name="value" val="&quot;android.permission.AUTHENTICATE_ACCOUNTS&quot;" />
+            <val name="apis" val="&quot;..22&quot;" />
+        </annotation>
+    </item>
+    <item name="android.accounts.AccountManager void setPassword(android.accounts.Account, java.lang.String)">
+        <annotation name="android.support.annotation.RequiresPermission">
+            <val name="value" val="&quot;android.permission.AUTHENTICATE_ACCOUNTS&quot;" />
+            <val name="apis" val="&quot;..22&quot;" />
+        </annotation>
+    </item>
+    <item name="android.accounts.AccountManager void setUserData(android.accounts.Account, java.lang.String, java.lang.String)">
+        <annotation name="android.support.annotation.RequiresPermission">
+            <val name="value" val="&quot;android.permission.AUTHENTICATE_ACCOUNTS&quot;" />
+            <val name="apis" val="&quot;..22&quot;" />
+        </annotation>
+    </item>
+    <item name="android.accounts.AccountManager android.accounts.Account[] getAccounts()">
+        <annotation name="android.support.annotation.RequiresPermission">
+            <val name="value" val="&quot;android.permission.GET_ACCOUNTS&quot;" />
+            <val name="apis" val="&quot;..22&quot;" />
+        </annotation>
+    </item>
+    <item name="android.accounts.AccountManager android.accounts.Account[] getAccountsByType(java.lang.String)">
+        <annotation name="android.support.annotation.RequiresPermission">
+            <val name="value" val="&quot;android.permission.GET_ACCOUNTS&quot;" />
+            <val name="apis" val="&quot;..22&quot;" />
+        </annotation>
+    </item>
+</root>
diff --git a/manual/android/content/annotations.xml b/manual/android/content/annotations.xml
new file mode 100644
index 0000000..a69e90d
--- /dev/null
+++ b/manual/android/content/annotations.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<root>
+  <item name="android.content.SharedPreferences java.lang.String getString(java.lang.String, java.lang.String)">
+    <annotation name="org.jetbrains.annotations.Contract">
+      <val name="value" val="&quot;_,!null-&gt;!null&quot;" />
+    </annotation>
+  </item>
+  <item name="android.content.SharedPreferences java.util.Set&lt;java.lang.String&gt; getStringSet(java.lang.String, java.util.Set&lt;java.lang.String&gt;)">
+    <annotation name="org.jetbrains.annotations.Contract">
+      <val name="value" val="&quot;_,!null-&gt;!null&quot;" />
+    </annotation>
+  </item>
+</root>
+
diff --git a/manual/android/location/annotations.xml b/manual/android/location/annotations.xml
new file mode 100644
index 0000000..9aa7540
--- /dev/null
+++ b/manual/android/location/annotations.xml
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+  <item name="android.location.LocationManager void removeProximityAlert(android.app.PendingIntent)">
+    <annotation name="android.support.annotation.RequiresPermission">
+      <val name="anyOf" val="{&quot;android.permission.ACCESS_COARSE_LOCATION&quot;, &quot;android.permission.ACCESS_FINE_LOCATION&quot;}" />
+      <val name="apis" val="&quot;..22&quot;" />
+    </annotation>
+  </item>
+  <item name="android.location.LocationManager void removeUpdates(android.location.LocationListener)">
+    <annotation name="android.support.annotation.RequiresPermission">
+      <val name="anyOf" val="{&quot;android.permission.ACCESS_COARSE_LOCATION&quot;, &quot;android.permission.ACCESS_FINE_LOCATION&quot;}" />
+      <val name="apis" val="&quot;..22&quot;" />
+    </annotation>
+  </item>
+</root>
diff --git a/manual/android/provider/annotations.xml b/manual/android/provider/annotations.xml
new file mode 100644
index 0000000..ab67042
--- /dev/null
+++ b/manual/android/provider/annotations.xml
@@ -0,0 +1,89 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<root>
+  <item name="android.provider.Browser BOOKMARKS_URI">
+    <annotation name="android.support.annotation.RequiresPermission.Read">
+      <val name="value" val="&quot;com.android.browser.permission.READ_HISTORY_BOOKMARKS&quot;" />
+      <val name="apis" val="&quot;..22&quot;" />
+    </annotation>
+    <annotation name="android.support.annotation.RequiresPermission.Write">
+      <val name="value" val="&quot;com.android.browser.permission.WRITE_HISTORY_BOOKMARKS&quot;" />
+      <val name="apis" val="&quot;..22&quot;" />
+    </annotation>
+  </item>
+  <item name="android.provider.Browser SEARCHES_URI">
+    <annotation name="android.support.annotation.RequiresPermission.Read">
+      <val name="value" val="&quot;com.android.browser.permission.READ_HISTORY_BOOKMARKS&quot;" />
+      <val name="apis" val="&quot;..22&quot;" />
+    </annotation>
+    <annotation name="android.support.annotation.RequiresPermission.Write">
+      <val name="value" val="&quot;com.android.browser.permission.WRITE_HISTORY_BOOKMARKS&quot;" />
+      <val name="apis" val="&quot;..22&quot;" />
+    </annotation>
+  </item>
+  <item name="android.provider.Browser android.database.Cursor getAllBookmarks(android.content.ContentResolver)">
+    <annotation name="android.support.annotation.RequiresPermission">
+      <val name="value" val="&quot;com.android.browser.permission.READ_HISTORY_BOOKMARKS&quot;" />
+      <val name="apis" val="&quot;..22&quot;" />
+    </annotation>
+  </item>
+  <item name="android.provider.Browser android.database.Cursor getAllVisitedUrls(android.content.ContentResolver)">
+    <annotation name="android.support.annotation.RequiresPermission">
+      <val name="value" val="&quot;com.android.browser.permission.READ_HISTORY_BOOKMARKS&quot;" />
+      <val name="apis" val="&quot;..22&quot;" />
+    </annotation>
+  </item>
+  <item name="android.provider.Browser boolean canClearHistory(android.content.ContentResolver)">
+    <annotation name="android.support.annotation.RequiresPermission">
+      <val name="value" val="&quot;com.android.browser.permission.READ_HISTORY_BOOKMARKS&quot;" />
+      <val name="apis" val="&quot;..22&quot;" />
+    </annotation>
+  </item>
+  <item name="android.provider.Browser void addSearchUrl(android.content.ContentResolver, java.lang.String)">
+    <annotation name="android.support.annotation.RequiresPermission">
+      <val name="allOf" val="{&quot;com.android.browser.permission.READ_HISTORY_BOOKMARKS&quot;, &quot;com.android.browser.permission.WRITE_HISTORY_BOOKMARKS&quot;}" />
+      <val name="apis" val="&quot;..22&quot;" />
+    </annotation>
+  </item>
+  <item name="android.provider.Browser void clearHistory(android.content.ContentResolver)">
+    <annotation name="android.support.annotation.RequiresPermission">
+      <val name="value" val="&quot;com.android.browser.permission.WRITE_HISTORY_BOOKMARKS&quot;" />
+      <val name="apis" val="&quot;..22&quot;" />
+    </annotation>
+  </item>
+  <item name="android.provider.Browser void clearSearches(android.content.ContentResolver)">
+    <annotation name="android.support.annotation.RequiresPermission">
+      <val name="value" val="&quot;com.android.browser.permission.WRITE_HISTORY_BOOKMARKS&quot;" />
+      <val name="apis" val="&quot;..22&quot;" />
+    </annotation>
+  </item>
+  <item name="android.provider.Browser void deleteFromHistory(android.content.ContentResolver, java.lang.String)">
+    <annotation name="android.support.annotation.RequiresPermission">
+      <val name="value" val="&quot;com.android.browser.permission.WRITE_HISTORY_BOOKMARKS&quot;" />
+      <val name="apis" val="&quot;..22&quot;" />
+    </annotation>
+  </item>
+  <item name="android.provider.Browser void deleteHistoryTimeFrame(android.content.ContentResolver, long, long)">
+    <annotation name="android.support.annotation.RequiresPermission">
+      <val name="value" val="&quot;com.android.browser.permission.WRITE_HISTORY_BOOKMARKS&quot;" />
+      <val name="apis" val="&quot;..22&quot;" />
+    </annotation>
+  </item>
+  <item name="android.provider.Browser void requestAllIcons(android.content.ContentResolver, java.lang.String, android.webkit.WebIconDatabase.IconListener)">
+    <annotation name="android.support.annotation.RequiresPermission">
+      <val name="value" val="&quot;com.android.browser.permission.READ_HISTORY_BOOKMARKS&quot;" />
+      <val name="apis" val="&quot;..22&quot;" />
+    </annotation>
+  </item>
+  <item name="android.provider.Browser void truncateHistory(android.content.ContentResolver)">
+    <annotation name="android.support.annotation.RequiresPermission">
+      <val name="allOf" val="{&quot;com.android.browser.permission.READ_HISTORY_BOOKMARKS&quot;, &quot;com.android.browser.permission.WRITE_HISTORY_BOOKMARKS&quot;}" />
+      <val name="apis" val="&quot;..22&quot;" />
+    </annotation>
+  </item>
+  <item name="android.provider.Browser void updateVisitedHistory(android.content.ContentResolver, java.lang.String, boolean)">
+    <annotation name="android.support.annotation.RequiresPermission">
+      <val name="allOf" val="{&quot;com.android.browser.permission.READ_HISTORY_BOOKMARKS&quot;, &quot;com.android.browser.permission.WRITE_HISTORY_BOOKMARKS&quot;}" />
+      <val name="apis" val="&quot;..22&quot;" />
+    </annotation>
+  </item>
+</root>
diff --git a/manual/android/support/design/widget/annotations.xml b/manual/android/support/design/widget/annotations.xml
new file mode 100644
index 0000000..2aedd81
--- /dev/null
+++ b/manual/android/support/design/widget/annotations.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<root>
+  <item name="android.support.design.widget.Snackbar.Duration">
+    <annotation name="android.support.annotation.IntDef">
+      <val name="value" val="{android.support.design.widget.Snackbar.LENGTH_INDEFINITE, android.support.design.widget.Snackbar.LENGTH_SHORT, android.support.design.widget.Snackbar.LENGTH_LONG}" />
+    </annotation>
+  </item>
+</root>
+
diff --git a/manual/android/text/annotations.xml b/manual/android/text/annotations.xml
new file mode 100644
index 0000000..0b15f6e
--- /dev/null
+++ b/manual/android/text/annotations.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<root>
+  <item name="android.text.TextUtils boolean isEmpty(java.lang.CharSequence)">
+    <annotation name="org.jetbrains.annotations.Contract">
+      <val name="value" val="&quot;null-&gt;true&quot;" />
+    </annotation>
+  </item>
+  <item name="android.text.TextUtils boolean stringOrSpannedString(java.lang.CharSequence)">
+    <annotation name="org.jetbrains.annotations.Contract">
+      <val name="value" val="&quot;null&gt;null;!null&gt;!null&quot;" />
+    </annotation>
+  </item>
+</root>
+
diff --git a/src/main/java/com/android/tools/lint/annotations/SdkUtils2.java b/src/main/java/com/android/tools/lint/annotations/SdkUtils2.java
new file mode 100644
index 0000000..8e46634
--- /dev/null
+++ b/src/main/java/com/android/tools/lint/annotations/SdkUtils2.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.lint.annotations;
+
+import com.android.annotations.NonNull;
+import com.android.annotations.Nullable;
+
+// Copy of SdkUtils but with a modification that isn't in SdkUtils yet
+
+public class SdkUtils2 {
+    /**
+     * Wraps the given text at the given line width, with an optional hanging
+     * indent.
+     *
+     * @param text          the text to be wrapped
+     * @param lineWidth     the number of characters to wrap the text to
+     * @param hangingIndent the hanging indent (to be used for the second and
+     *                      subsequent lines in each paragraph, or null if not known
+     * @return the string, wrapped
+     */
+    @NonNull
+    public static String wrap(
+            @NonNull String text,
+            int lineWidth,
+            @Nullable String hangingIndent) {
+        return wrap(text, lineWidth, lineWidth, hangingIndent);
+    }
+
+    /**
+     * Wraps the given text at the given line width, with an optional hanging
+     * indent.
+     *
+     * @param text           the text to be wrapped
+     * @param firstLineWidth the line width to wrap the text to (on the first line)
+     * @param nextLineWidth  the line width to wrap the text to (on subsequent lines).
+     *                       This does not include the hanging indent, if any.
+     * @param hangingIndent  the hanging indent (to be used for the second and
+     *                       subsequent lines in each paragraph, or null if not known
+     * @return the string, wrapped
+     */
+    @NonNull
+    public static String wrap(
+            @NonNull String text,
+            int firstLineWidth,
+            int nextLineWidth,
+            @Nullable String hangingIndent) {
+        if (hangingIndent == null) {
+            hangingIndent = "";
+        }
+        int lineWidth = firstLineWidth;
+        int explanationLength = text.length();
+        StringBuilder sb = new StringBuilder(explanationLength * 2);
+        int index = 0;
+
+        while (index < explanationLength) {
+            int lineEnd = text.indexOf('\n', index);
+            int next;
+
+            if (lineEnd != -1 && (lineEnd - index) < lineWidth) {
+                next = lineEnd + 1;
+            } else {
+                // Line is longer than available width; grab as much as we can
+                lineEnd = Math.min(index + lineWidth, explanationLength);
+                if (lineEnd - index < lineWidth) {
+                    next = explanationLength;
+                } else {
+                    // then back up to the last space
+                    int lastSpace = text.lastIndexOf(' ', lineEnd);
+                    if (lastSpace > index) {
+                        lineEnd = lastSpace;
+                        next = lastSpace + 1;
+                    } else {
+                        // No space anywhere on the line: it contains something wider than
+                        // can fit (like a long URL) so just hard break it
+                        next = lineEnd;
+                    }
+                }
+            }
+
+            if (sb.length() > 0) {
+                sb.append(hangingIndent);
+            } else {
+                lineWidth = nextLineWidth - hangingIndent.length();
+            }
+
+            sb.append(text.substring(index, lineEnd));
+            sb.append('\n');
+            index = next;
+        }
+
+        return sb.toString();
+    }
+}
diff --git a/src/main/java/com/android/tools/lint/checks/infrastructure/ClassName.kt b/src/main/java/com/android/tools/lint/checks/infrastructure/ClassName.kt
new file mode 100644
index 0000000..38e79c5
--- /dev/null
+++ b/src/main/java/com/android/tools/lint/checks/infrastructure/ClassName.kt
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.lint.checks.infrastructure
+
+// ------------------------------------------------------------------------------------------
+// ------------------------------------------------------------------------------------------
+//
+// Copy from lint; temporarily included in metalava sources since we need the latest
+// version (from lint 3.1) which isn't available on maven.google.com yet. Delete this
+// and replace with direct usage once it is.
+//
+// ------------------------------------------------------------------------------------------
+// ------------------------------------------------------------------------------------------
+
+import java.util.regex.Pattern
+
+/** A pair of package name and class name inferred from Java or Kotlin source code */
+class ClassName(source: String) {
+    val packageName: String?
+    val className: String?
+
+    init {
+        val withoutComments = stripComments(source)
+        packageName = getPackage(withoutComments)
+        className = getClassName(withoutComments)
+    }
+
+    fun packageNameWithDefault() = packageName ?: ""
+}
+
+/**
+ * Strips line and block comments from the given Java or Kotlin source file
+ */
+@Suppress("LocalVariableName")
+fun stripComments(source: String, stripLineComments: Boolean = true): String {
+    val sb = StringBuilder(source.length)
+    var state = 0
+    val INIT = 0
+    val INIT_SLASH = 1
+    val LINE_COMMENT = 2
+    val BLOCK_COMMENT = 3
+    val BLOCK_COMMENT_ASTERISK = 4
+    val IN_STRING = 5
+    val IN_STRING_ESCAPE = 6
+    val IN_CHAR = 7
+    val AFTER_CHAR = 8
+    for (i in 0 until source.length) {
+        val c = source[i]
+        when (state) {
+            INIT -> {
+                when (c) {
+                    '/' -> state = INIT_SLASH
+                    '"' -> {
+                        state = IN_STRING
+                        sb.append(c)
+                    }
+                    '\'' -> {
+                        state = IN_CHAR
+                        sb.append(c)
+                    }
+                    else -> sb.append(c)
+                }
+            }
+            INIT_SLASH -> {
+                when {
+                    c == '*' -> state = BLOCK_COMMENT
+                    c == '/' && stripLineComments -> state = LINE_COMMENT
+                    else -> {
+                        state = INIT
+                        sb.append('/') // because we skipped it in init
+                        sb.append(c)
+                    }
+                }
+            }
+            LINE_COMMENT -> {
+                when (c) {
+                    '\n' -> state = INIT
+                }
+            }
+            BLOCK_COMMENT -> {
+                when (c) {
+                    '*' -> state = BLOCK_COMMENT_ASTERISK
+                }
+            }
+            BLOCK_COMMENT_ASTERISK -> {
+                state = when (c) {
+                    '/' -> INIT
+                    '*' -> BLOCK_COMMENT_ASTERISK
+                    else -> BLOCK_COMMENT
+                }
+            }
+            IN_STRING -> {
+                when (c) {
+                    '\\' -> state = IN_STRING_ESCAPE
+                    '"' -> state = INIT
+                }
+                sb.append(c)
+            }
+            IN_STRING_ESCAPE -> {
+                sb.append(c)
+                state = IN_STRING
+            }
+            IN_CHAR -> {
+                if (c != '\\') {
+                    state = AFTER_CHAR
+                }
+                sb.append(c)
+            }
+            AFTER_CHAR -> {
+                sb.append(c)
+                if (c == '\\') {
+                    state = INIT
+                }
+            }
+        }
+    }
+
+    return sb.toString()
+}
+
+private val PACKAGE_PATTERN = Pattern.compile("""package\s+([\S&&[^;]]*)""")
+
+private val CLASS_PATTERN = Pattern.compile("""(class|interface|enum|object)+?\s*([^\s:(]+)""",
+        Pattern.MULTILINE)
+
+fun getPackage(source: String): String? {
+    val matcher = PACKAGE_PATTERN.matcher(source)
+    return if (matcher.find()) {
+        matcher.group(1).trim { it <= ' ' }
+    } else {
+        null
+    }
+}
+
+fun getClassName(source: String): String? {
+    val matcher = CLASS_PATTERN.matcher(source.replace('\n', ' '))
+    var start = 0
+    while (matcher.find(start)) {
+        val cls = matcher.group(2)
+        val groupStart = matcher.start(2)
+
+        // Make sure this "class" reference isn't part of an annotation on the class
+        // referencing a class literal -- Foo.class, or in Kotlin, Foo::class.java)
+        if (groupStart == 0 || source[groupStart-1] != '.' && source[groupStart-1] != ':') {
+            val trimmed = cls.trim { it <= ' ' }
+            val typeParameter = trimmed.indexOf('<')
+            return if (typeParameter != -1) {
+                trimmed.substring(0, typeParameter)
+            } else {
+                trimmed
+            }
+        }
+        start = matcher.end(2)
+    }
+
+    return null
+}
diff --git a/src/main/java/com/android/tools/metalava/AnnotationStatistics.kt b/src/main/java/com/android/tools/metalava/AnnotationStatistics.kt
new file mode 100644
index 0000000..f82e67c
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/AnnotationStatistics.kt
@@ -0,0 +1,429 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava
+
+import com.android.SdkConstants
+import com.android.tools.metalava.doclava1.ApiPredicate
+import com.android.tools.metalava.model.ClassItem
+import com.android.tools.metalava.model.Codebase
+import com.android.tools.metalava.model.FieldItem
+import com.android.tools.metalava.model.Item
+import com.android.tools.metalava.model.MemberItem
+import com.android.tools.metalava.model.MethodItem
+import com.android.tools.metalava.model.PackageItem
+import com.android.tools.metalava.model.ParameterItem
+import com.android.tools.metalava.model.visitors.ApiVisitor
+import com.google.common.io.ByteStreams
+import org.objectweb.asm.ClassReader
+import org.objectweb.asm.Type
+import org.objectweb.asm.tree.AbstractInsnNode
+import org.objectweb.asm.tree.ClassNode
+import org.objectweb.asm.tree.FieldInsnNode
+import org.objectweb.asm.tree.MethodInsnNode
+import org.objectweb.asm.tree.MethodNode
+import java.io.File
+import java.io.IOException
+import java.io.PrintWriter
+import java.util.zip.ZipFile
+
+const val CLASS_COLUMN_WIDTH = 60
+const val COUNT_COLUMN_WIDTH = 16
+const val USAGE_REPORT_MAX_ROWS = 15
+
+class AnnotationStatistics(val api: Codebase) {
+    val apiFilter = ApiPredicate(api)
+
+    /** Measure the coverage statistics for the API */
+    fun count() {
+        var allMethods = 0
+        var annotatedMethods = 0
+        var allFields = 0
+        var annotatedFields = 0
+        var allParameters = 0
+        var annotatedParameters = 0
+
+        api.accept(object : ApiVisitor(api) {
+            override fun skip(item: Item): Boolean {
+                if (options.omitRuntimePackageStats && item is PackageItem) {
+                    val name = item.qualifiedName()
+                    if (name.startsWith("java.") ||
+                        name.startsWith("javax.") ||
+                        name.startsWith("kotlin.") ||
+                        name.startsWith("kotlinx.")
+                    ) {
+                        return true
+                    }
+                }
+                return super.skip(item)
+            }
+
+            override fun visitParameter(parameter: ParameterItem) {
+                allParameters++
+                if (parameter.modifiers.annotations().any { it.isNonNull() || it.isNullable() }) {
+                    annotatedParameters++
+                }
+            }
+
+            override fun visitField(field: FieldItem) {
+                allFields++
+                if (field.modifiers.annotations().any { it.isNonNull() || it.isNullable() }) {
+                    annotatedFields++
+                }
+            }
+
+            override fun visitMethod(method: MethodItem) {
+                allMethods++
+                if (method.modifiers.annotations().any { it.isNonNull() || it.isNullable() }) {
+                    annotatedMethods++
+                }
+            }
+        })
+
+        options.stdout.println()
+        options.stdout.println(
+            """
+            Nullness Annotation Coverage Statistics:
+            $annotatedMethods out of $allMethods methods were annotated (${percent(annotatedMethods, allMethods)}%)
+            $annotatedFields out of $allFields fields were annotated (${percent(annotatedFields, allFields)}%)
+            $annotatedParameters out of $allParameters parameters were annotated (${percent(
+                annotatedParameters,
+                allParameters
+            )}%)
+            """.trimIndent()
+        )
+    }
+
+    private fun percent(numerator: Int, denominator: Int): Int {
+        return if (denominator == 0) {
+            0
+        } else {
+            numerator * 100 / denominator
+        }
+    }
+
+    fun measureCoverageOf(classPath: List<File>) {
+        val used = HashMap<MemberItem, Int>(1000)
+
+        for (entry in classPath) {
+            recordUsages(used, entry, entry.path)
+        }
+
+        // Keep only those items where there is at least one un-annotated element in the API
+        val filtered = used.keys.filter {
+            !it.hasNullnessInfo()
+        }
+
+        val referenceCount = used.size
+        val missingCount = filtered.size
+        val annotatedCount = used.size - filtered.size
+
+        // Sort by descending usage
+        val sorted = filtered.sortedWith(Comparator { o1, o2 ->
+            // Sort first by descending count, then increasing alphabetical
+            val delta = used[o2]!! - used[o1]!!
+            if (delta != 0) {
+                return@Comparator delta
+            }
+            o1.toString().compareTo(o2.toString())
+        })
+
+        // High level summary
+        options.stdout.println()
+        options.stdout.println(
+            "$missingCount methods and fields were missing nullness annotations out of " +
+                    "$referenceCount total API references."
+        )
+        options.stdout.println("API nullness coverage is ${percent(annotatedCount, referenceCount)}%")
+        options.stdout.println()
+
+        reportTopUnannotatedClasses(sorted, used)
+        printMemberTable(sorted, used)
+    }
+
+    private fun reportTopUnannotatedClasses(sorted: List<MemberItem>, used: HashMap<MemberItem, Int>) {
+        // Aggregate class counts
+        val classCount = mutableMapOf<Item, Int>()
+        for (item in sorted) {
+            val containingClass = item.containingClass()
+            val itemCount = used[item]!!
+            val count = classCount[containingClass]
+            if (count == null) {
+                classCount[containingClass] = itemCount
+            } else {
+                classCount[containingClass] = count + itemCount
+            }
+        }
+
+        // Print out top entries
+        val classes = classCount.keys.sortedWith(Comparator { o1, o2 ->
+            // Sort first by descending count, then increasing alphabetical
+            val delta = classCount[o2]!! - classCount[o1]!!
+            if (delta != 0) {
+                return@Comparator delta
+            }
+            o1.toString().compareTo(o2.toString())
+        })
+
+        printClassTable(classes, classCount)
+    }
+
+    /** Print table in clean Markdown table syntax */
+    private fun printTable(
+        labelHeader: String,
+        countHeader: String,
+        items: List<Item>,
+        getLabel: (Item) -> String,
+        getCount: (Item) -> Int,
+        printer: PrintWriter = options.stdout
+    ) {
+        // Print table in clean Markdown table syntax
+        edge(printer, CLASS_COLUMN_WIDTH + 2, COUNT_COLUMN_WIDTH + 2)
+        printer.printf(
+            "| %-${CLASS_COLUMN_WIDTH}s | %${COUNT_COLUMN_WIDTH}s |\n",
+            labelHeader, countHeader
+        )
+        separator(printer, CLASS_COLUMN_WIDTH + 2, COUNT_COLUMN_WIDTH + 2, rightJustify = true)
+
+        for (i in 0 until items.size) {
+            val item = items[i]
+            val label = getLabel(item)
+            val count = getCount(item)
+            printer.printf(
+                "| %-${CLASS_COLUMN_WIDTH}s | %${COUNT_COLUMN_WIDTH}d |\n",
+                truncate(label, CLASS_COLUMN_WIDTH), count
+            )
+
+            if (i == USAGE_REPORT_MAX_ROWS) {
+                printer.printf(
+                    "| %-${CLASS_COLUMN_WIDTH}s | %${COUNT_COLUMN_WIDTH}s |\n",
+                    "... (${items.size - USAGE_REPORT_MAX_ROWS} more items", ""
+                )
+                break
+            }
+        }
+        edge(printer, CLASS_COLUMN_WIDTH + 2, COUNT_COLUMN_WIDTH + 2)
+    }
+
+    private fun printClassTable(classes: List<Item>, classCount: MutableMap<Item, Int>) {
+        printTable("Qualified Class Name",
+            "Usage Count",
+            classes,
+            { (it as ClassItem).qualifiedName() },
+            { classCount[it]!! })
+    }
+
+    private fun printMemberTable(
+        sorted: List<MemberItem>, used: HashMap<MemberItem, Int>,
+        printer: PrintWriter = options.stdout
+    ) {
+        // Top APIs
+        printer.println("\nTop referenced un-annotated members:\n")
+
+        printTable(
+            "Member",
+            "Usage Count",
+            sorted,
+            {
+                val member = it as MemberItem
+                "${member.containingClass().simpleName()}.${member.name()}${if (member is MethodItem) "(${member.parameters().joinToString {
+                    it.type().toSimpleType()
+                }})" else ""}"
+            },
+            { used[it]!! },
+            printer
+        )
+    }
+
+    private fun dashes(printer: PrintWriter, max: Int) {
+        for (count in 0 until max) {
+            printer.print('-')
+        }
+    }
+
+    private fun edge(printer: PrintWriter, column1: Int, column2: Int) {
+        printer.print("|")
+        dashes(printer, column1)
+        printer.print("|")
+        dashes(printer, column2)
+        printer.print("|")
+        printer.println()
+    }
+
+    private fun separator(printer: PrintWriter, cell1: Int, cell2: Int, rightJustify: Boolean = false) {
+        printer.print('|')
+        dashes(printer, cell1)
+        printer.print('|')
+        if (rightJustify) {
+            dashes(printer, cell2 - 1)
+            // Markdown syntax to force column to be right justified instead of left justified
+            printer.print(":|")
+        } else {
+            dashes(printer, cell2)
+            printer.print('|')
+        }
+        printer.println()
+    }
+
+    private fun truncate(string: String, maxLength: Int): String {
+        if (string.length < maxLength) {
+            return string
+        }
+
+        return string.substring(0, maxLength - 3) + "..."
+    }
+
+    private fun recordUsages(used: MutableMap<MemberItem, Int>, file: File, path: String) {
+        when {
+            file.name.endsWith(SdkConstants.DOT_JAR) -> try {
+                ZipFile(file).use({ jar ->
+                    val enumeration = jar.entries()
+                    while (enumeration.hasMoreElements()) {
+                        val entry = enumeration.nextElement()
+                        if (entry.name.endsWith(SdkConstants.DOT_CLASS)) {
+                            try {
+                                jar.getInputStream(entry).use({ `is` ->
+                                    val bytes = ByteStreams.toByteArray(`is`)
+                                    if (bytes != null) {
+                                        recordUsages(used, bytes, path + ":" + entry.name)
+                                    }
+                                })
+                            } catch (e: Exception) {
+                                options.stdout.println("Could not read jar file entry ${entry.name} from $file: $e")
+                            }
+                        }
+                    }
+                })
+            } catch (e: IOException) {
+                options.stdout.println("Could not read jar file contents from $file: $e")
+            }
+            file.isDirectory -> {
+                val listFiles = file.listFiles()
+                listFiles?.forEach {
+                    recordUsages(used, it, it.path)
+                }
+            }
+            file.path.endsWith(SdkConstants.DOT_CLASS) -> {
+                val bytes = file.readBytes()
+                recordUsages(used, bytes, file.path)
+            }
+            else -> options.stdout.println("Ignoring entry $file")
+        }
+    }
+
+    private fun recordUsages(used: MutableMap<MemberItem, Int>, bytes: ByteArray, path: String) {
+        val reader: ClassReader
+        val classNode: ClassNode
+        try {
+            reader = ClassReader(bytes)
+            classNode = ClassNode()
+            reader.accept(classNode, 0)
+        } catch (t: Throwable) {
+            options.stderr.println("Error processing $path: broken class file?")
+            return
+        }
+
+        val skipJava = options.omitRuntimePackageStats
+
+        for (methodObject in classNode.methods) {
+            val method = methodObject as MethodNode
+            val nodes = method.instructions
+            for (i in 0 until nodes.size()) {
+                val instruction = nodes.get(i)
+                val type = instruction.type
+                if (type == AbstractInsnNode.METHOD_INSN) {
+                    val call = instruction as MethodInsnNode
+                    if (skipJava && isSkippableOwner(call.owner)) {
+                        continue
+                    }
+                    val item = findMethod(call)
+                    item?.let {
+                        val count = used[it]
+                        if (count == null) {
+                            used[it] = 1
+                        } else {
+                            used[it] = count + 1
+                        }
+                    }
+                } else if (type == AbstractInsnNode.FIELD_INSN) {
+                    val field = instruction as FieldInsnNode
+                    if (skipJava && isSkippableOwner(field.owner)) {
+                        continue
+                    }
+                    val item = findField(field)
+                    item?.let {
+                        val count = used[it]
+                        if (count == null) {
+                            used[it] = 1
+                        } else {
+                            used[it] = count + 1
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    private fun isSkippableOwner(owner: String) =
+        owner.startsWith("java/") ||
+                owner.startsWith("javax/") ||
+                owner.startsWith("kotlin") ||
+                owner.startsWith("kotlinx/")
+
+    private fun findField(node: FieldInsnNode): FieldItem? {
+        val cls = findClass(node.owner) ?: return null
+        val field = cls.findField(node.name)
+        return if (field != null && apiFilter.test(field)) {
+            field
+        } else {
+            null
+        }
+    }
+
+    private fun findClass(owner: String): ClassItem? {
+        val className = owner.replace('/', '.').replace('$', '.')
+        val cls = api.findClass(className)
+        return if (cls != null && apiFilter.test(cls)) {
+            cls
+        } else {
+            null
+        }
+    }
+
+    private fun findMethod(node: MethodInsnNode): MethodItem? {
+        val cls = findClass(node.owner) ?: return null
+        val types = Type.getArgumentTypes(node.desc)
+        val parameters = if (types.isNotEmpty()) {
+            val sb = StringBuilder()
+            for (type in types) {
+                if (!sb.isEmpty()) {
+                    sb.append(", ")
+                }
+                sb.append(type.className.replace('/', '.').replace('$', '.'))
+            }
+            sb.toString()
+        } else {
+            ""
+        }
+        val methodName = if (node.name == "<init>") cls.simpleName() else node.name
+        val method = cls.findMethod(methodName, parameters)
+        return if (method != null && apiFilter.test(method)) {
+            method
+        } else {
+            null
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/AnnotationsMerger.kt b/src/main/java/com/android/tools/metalava/AnnotationsMerger.kt
new file mode 100644
index 0000000..d42bdac
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/AnnotationsMerger.kt
@@ -0,0 +1,734 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava
+
+import com.android.SdkConstants.AMP_ENTITY
+import com.android.SdkConstants.APOS_ENTITY
+import com.android.SdkConstants.ATTR_NAME
+import com.android.SdkConstants.DOT_CLASS
+import com.android.SdkConstants.DOT_JAR
+import com.android.SdkConstants.DOT_XML
+import com.android.SdkConstants.DOT_ZIP
+import com.android.SdkConstants.GT_ENTITY
+import com.android.SdkConstants.INT_DEF_ANNOTATION
+import com.android.SdkConstants.LT_ENTITY
+import com.android.SdkConstants.QUOT_ENTITY
+import com.android.SdkConstants.STRING_DEF_ANNOTATION
+import com.android.SdkConstants.TYPE_DEF_FLAG_ATTRIBUTE
+import com.android.SdkConstants.TYPE_DEF_VALUE_ATTRIBUTE
+import com.android.SdkConstants.VALUE_TRUE
+import com.android.annotations.NonNull
+import com.android.tools.lint.annotations.ApiDatabase
+import com.android.tools.lint.annotations.Extractor.ANDROID_INT_DEF
+import com.android.tools.lint.annotations.Extractor.ANDROID_NOTNULL
+import com.android.tools.lint.annotations.Extractor.ANDROID_NULLABLE
+import com.android.tools.lint.annotations.Extractor.ANDROID_STRING_DEF
+import com.android.tools.lint.annotations.Extractor.ATTR_PURE
+import com.android.tools.lint.annotations.Extractor.ATTR_VAL
+import com.android.tools.lint.annotations.Extractor.IDEA_CONTRACT
+import com.android.tools.lint.annotations.Extractor.IDEA_MAGIC
+import com.android.tools.lint.annotations.Extractor.IDEA_NOTNULL
+import com.android.tools.lint.annotations.Extractor.IDEA_NULLABLE
+import com.android.tools.lint.annotations.Extractor.SUPPORT_NOTNULL
+import com.android.tools.lint.annotations.Extractor.SUPPORT_NULLABLE
+import com.android.tools.lint.detector.api.LintUtils.getChildren
+import com.android.tools.metalava.model.AnnotationAttribute
+import com.android.tools.metalava.model.AnnotationAttributeValue
+import com.android.tools.metalava.model.AnnotationItem
+import com.android.tools.metalava.model.ClassItem
+import com.android.tools.metalava.model.Codebase
+import com.android.tools.metalava.model.DefaultAnnotationValue
+import com.android.tools.metalava.model.Item
+import com.android.tools.metalava.model.MethodItem
+import com.android.tools.metalava.model.psi.PsiAnnotationItem
+import com.android.utils.XmlUtils
+import com.google.common.base.Charsets
+import com.google.common.base.Splitter
+import com.google.common.collect.ImmutableSet
+import com.google.common.io.ByteStreams
+import com.google.common.io.Closeables
+import com.google.common.io.Files
+import com.google.common.xml.XmlEscapers
+import org.w3c.dom.Document
+import org.w3c.dom.Element
+import org.w3c.dom.Node
+import org.xml.sax.SAXParseException
+import java.io.File
+import java.io.FileInputStream
+import java.io.IOException
+import java.lang.reflect.Field
+import java.util.ArrayList
+import java.util.Collections
+import java.util.HashMap
+import java.util.jar.JarInputStream
+import java.util.regex.Pattern
+import java.util.zip.ZipEntry
+import kotlin.Comparator
+
+/** Merges annotations into classes already registered in the given [Codebase] */
+class AnnotationsMerger(
+    private val codebase: Codebase,
+    private val apiFilter: ApiDatabase?,
+    private val listIgnored: Boolean = true
+) {
+    fun merge(mergeAnnotations: List<File>) {
+        mergeAnnotations.forEach { mergeExisting(it) }
+    }
+
+    private fun mergeExisting(@NonNull file: File) {
+        if (file.isDirectory) {
+            val files = file.listFiles()
+            if (files != null) {
+                for (child in files) {
+                    mergeExisting(child)
+                }
+            }
+        } else if (file.isFile) {
+            if (file.path.endsWith(DOT_JAR) || file.path.endsWith(DOT_ZIP)) {
+                mergeFromJar(file)
+            } else if (file.path.endsWith(DOT_XML)) {
+                try {
+                    val xml = Files.asCharSource(file, Charsets.UTF_8).read()
+                    mergeAnnotationsXml(file.path, xml)
+                } catch (e: IOException) {
+                    error("Aborting: I/O problem during transform: " + e.toString())
+                }
+
+            }
+        }
+    }
+
+    private fun mergeFromJar(@NonNull jar: File) {
+        // Reads in an existing annotations jar and merges in entries found there
+        // with the annotations analyzed from source.
+        var zis: JarInputStream? = null
+        try {
+            val fis = FileInputStream(jar)
+            zis = JarInputStream(fis)
+            var entry: ZipEntry? = zis.nextEntry
+            while (entry != null) {
+                if (entry.name.endsWith(".xml")) {
+                    val bytes = ByteStreams.toByteArray(zis)
+                    val xml = String(bytes, Charsets.UTF_8)
+                    mergeAnnotationsXml(jar.path + ": " + entry, xml)
+                }
+                entry = zis.nextEntry
+            }
+        } catch (e: IOException) {
+            error("Aborting: I/O problem during transform: " + e.toString())
+        } finally {
+            try {
+                Closeables.close(zis, true /* swallowIOException */)
+            } catch (e: IOException) {
+                // cannot happen
+            }
+        }
+    }
+
+    private fun mergeAnnotationsXml(@NonNull path: String, @NonNull xml: String) {
+        try {
+            val document = XmlUtils.parseDocument(xml, false)
+            mergeDocument(document)
+        } catch (e: Exception) {
+            var message = "Failed to merge " + path + ": " + e.toString()
+            if (e is SAXParseException) {
+                message = "Line " + e.lineNumber + ":" + e.columnNumber + ": " + message
+            }
+            error(message)
+            if (e !is IOException) {
+                e.printStackTrace()
+            }
+        }
+    }
+
+    internal fun error(message: String) {
+        // TODO: Integrate with metalava error facility
+        options.stderr.println("Error: " + message)
+    }
+
+    internal fun warning(message: String) {
+        options.stdout.println("Warning: " + message)
+    }
+
+    @Suppress("PrivatePropertyName")
+    private val XML_SIGNATURE: Pattern = Pattern.compile(
+        // Class (FieldName | Type? Name(ArgList) Argnum?)
+        //"(\\S+) (\\S+|(.*)\\s+(\\S+)\\((.*)\\)( \\d+)?)");
+        "(\\S+) (\\S+|((.*)\\s+)?(\\S+)\\((.*)\\)( \\d+)?)"
+    )
+
+    private fun mergeDocument(@NonNull document: Document) {
+
+        val root = document.documentElement
+        val rootTag = root.tagName
+        assert(rootTag == "root") { rootTag }
+
+        for (item in getChildren(root)) {
+            var signature: String? = item.getAttribute(ATTR_NAME)
+            if (signature == null || signature == "null") {
+                continue // malformed item
+            }
+
+            signature = unescapeXml(signature)
+            if (signature == "java.util.Calendar int get(int)") {
+                // https://youtrack.jetbrains.com/issue/IDEA-137385
+                continue
+            } else if (signature == "java.util.Calendar void set(int, int, int) 1"
+                || signature == "java.util.Calendar void set(int, int, int, int, int) 1"
+                || signature == "java.util.Calendar void set(int, int, int, int, int, int) 1"
+            ) {
+                // http://b.android.com/76090
+                continue
+            }
+
+            val matcher = XML_SIGNATURE.matcher(signature)
+            if (matcher.matches()) {
+                val containingClass = matcher.group(1)
+                if (containingClass == null) {
+                    warning("Could not find class for " + signature)
+                    continue
+                }
+
+                if (apiFilter != null &&
+                    !hasHistoricData(item) &&
+                    !apiFilter.hasClass(containingClass)
+                ) {
+                    if (listIgnored) {
+                        warning("Skipping imported element because it is not part of the API file: $containingClass")
+                    }
+                    continue
+                }
+
+                val classItem = codebase.findClass(containingClass)
+                if (classItem == null) {
+                    warning("Could not find class $containingClass; omitting annotations merge")
+                    continue
+                }
+
+                val methodName = matcher.group(5)
+                if (methodName != null) {
+                    val parameters = matcher.group(6)
+                    val parameterIndex =
+                        if (matcher.group(7) != null) {
+                            Integer.parseInt(matcher.group(7).trim())
+                        } else {
+                            -1
+                        }
+                    mergeMethodOrParameter(item, containingClass, classItem, methodName, parameterIndex, parameters)
+                } else {
+                    val fieldName = matcher.group(2)
+                    mergeField(item, containingClass, classItem, fieldName)
+                }
+            } else if (signature.indexOf(' ') == -1 && signature.indexOf('.') != -1) {
+                // Must be just a class
+                val containingClass = signature
+                if (apiFilter != null &&
+                    !hasHistoricData(item) &&
+                    !apiFilter.hasClass(containingClass)
+                ) {
+                    if (listIgnored) {
+                        warning("Skipping imported element because it is not part of the API file: $containingClass")
+                    }
+                    continue
+                }
+
+                val classItem = codebase.findClass(containingClass)
+                if (classItem == null) {
+                    warning("Could not find class $containingClass; omitting annotations merge")
+                    continue
+                }
+
+                mergeAnnotations(item, classItem)
+            } else {
+                warning("No merge match for signature " + signature)
+            }
+        }
+    }
+
+    // The parameter declaration used in XML files should not have duplicated spaces,
+    // and there should be no space after commas (we can't however strip out all spaces,
+    // since for example the spaces around the "extends" keyword needs to be there in
+    // types like Map<String,? extends Number>
+    private fun fixParameterString(parameters: String): String {
+        return parameters.replace("  ", " ").replace(", ", ",").replace("?super", "? super ")
+            .replace("?extends", "? extends ")
+    }
+
+    private fun mergeMethodOrParameter(
+        item: Element, containingClass: String, classItem: ClassItem,
+        methodName: String, parameterIndex: Int,
+        parameters: String
+    ) {
+        @Suppress("NAME_SHADOWING")
+        val parameters = fixParameterString(parameters)
+
+        if (apiFilter != null &&
+            !hasHistoricData(item) &&
+            !apiFilter.hasMethod(containingClass, methodName, parameters)
+        ) {
+            if (listIgnored) {
+                warning(
+                    "Skipping imported element because it is not part of the API file: "
+                            + containingClass + "#" + methodName + "(" + parameters + ")"
+                )
+            }
+            return
+        }
+
+        val methodItem: MethodItem? = classItem.findMethod(methodName, parameters)
+        if (methodItem == null) {
+            warning("Could not find class $methodName($parameters) in $containingClass; omitting annotations merge")
+            return
+        }
+
+        if (parameterIndex != -1) {
+            val parameterItem = methodItem.parameters()[parameterIndex]
+
+            if ("java.util.Calendar" == containingClass && "set" == methodName
+                && parameterIndex > 0
+            ) {
+                // Skip the metadata for Calendar.set(int, int, int+); see
+                // https://code.google.com/p/android/issues/detail?id=73982
+                return
+            }
+
+            mergeAnnotations(item, parameterItem)
+        } else {
+            // Annotation on the method itself
+            mergeAnnotations(item, methodItem)
+        }
+    }
+
+    private fun mergeField(item: Element, containingClass: String, classItem: ClassItem, fieldName: String) {
+        if (apiFilter != null &&
+            !hasHistoricData(item) &&
+            !apiFilter.hasField(containingClass, fieldName)
+        ) {
+            if (listIgnored) {
+                warning(
+                    "Skipping imported element because it is not part of the API file: "
+                            + containingClass + "#" + fieldName
+                )
+            }
+        } else {
+            val fieldItem = classItem.findField(fieldName)
+            if (fieldItem == null) {
+                warning("Could not find field $fieldName in $containingClass; omitting annotations merge")
+                return
+            }
+
+            mergeAnnotations(item, fieldItem)
+        }
+    }
+
+    private fun getAnnotationName(element: Element): String {
+        val tagName = element.tagName
+        assert(tagName == "annotation") { tagName }
+
+        val qualifiedName = element.getAttribute(ATTR_NAME)
+        assert(qualifiedName != null && !qualifiedName.isEmpty())
+        return qualifiedName
+    }
+
+    private fun mergeAnnotations(xmlElement: Element, item: Item): Int {
+        var count = 0
+
+        loop@ for (annotationElement in getChildren(xmlElement)) {
+            val qualifiedName = getAnnotationName(annotationElement)
+            if (!AnnotationItem.isSignificantAnnotation(qualifiedName)) {
+                continue
+            }
+            var haveNullable = false
+            var haveNotNull = false
+            for (existing in item.modifiers.annotations()) {
+                val name = existing.qualifiedName() ?: continue
+                if (isNonNull(name)) {
+                    haveNotNull = true
+                }
+                if (isNullable(name)) {
+                    haveNullable = true
+                }
+                if (name == qualifiedName) {
+                    continue@loop
+                }
+            }
+
+            // Make sure we don't have a conflict between nullable and not nullable
+            if (isNonNull(qualifiedName) && haveNullable || isNullable(qualifiedName) && haveNotNull) {
+                warning("Found both @Nullable and @NonNull after import for " + item)
+                continue
+            }
+
+            val annotationItem = createAnnotation(annotationElement) ?: continue
+            item.mutableModifiers().addAnnotation(annotationItem)
+            count++
+        }
+
+        return count
+    }
+
+    /** Reads in annotation data from an XML item (using IntelliJ IDE's external annotations XML format) and
+     * creates a corresponding [AnnotationItem], performing some "translations" in the process (e.g. mapping
+     * from IntelliJ annotations like `org.jetbrains.annotations.Nullable` to `android.support.annotation.Nullable`,
+     * as well as dropping constants from typedefs that aren't included according to the [apiFilter]. */
+    private fun createAnnotation(annotationElement: Element): AnnotationItem? {
+        val tagName = annotationElement.tagName
+        assert(tagName == "annotation") { tagName }
+        val name = annotationElement.getAttribute(ATTR_NAME)
+        assert(name != null && !name.isEmpty())
+        when {
+            name == IDEA_MAGIC -> {
+                val children = getChildren(annotationElement)
+                assert(children.size == 1) { children.size }
+                val valueElement = children[0]
+                val valName = valueElement.getAttribute(ATTR_NAME)
+                var value = valueElement.getAttribute(ATTR_VAL)
+                val flagsFromClass = valName == "flagsFromClass"
+                val flag = valName == "flags" || flagsFromClass
+                if (valName == "valuesFromClass" || flagsFromClass) {
+                    // Not supported
+                    var found = false
+                    if (value.endsWith(DOT_CLASS)) {
+                        val clsName = value.substring(0, value.length - DOT_CLASS.length)
+                        val sb = StringBuilder()
+                        sb.append('{')
+
+                        var reflectionFields: Array<Field>? = null
+                        try {
+                            val cls = Class.forName(clsName)
+                            reflectionFields = cls.declaredFields
+                        } catch (ignore: Exception) {
+                            // Class not available: not a problem. We'll rely on API filter.
+                            // It's mainly used for sorting anyway.
+                        }
+
+                        if (apiFilter != null) {
+                            // Search in API database
+                            var fields: Set<String>? = apiFilter.getDeclaredIntFields(clsName)
+                            if ("java.util.zip.ZipEntry" == clsName) {
+                                // The metadata says valuesFromClass ZipEntry, and unfortunately
+                                // that class implements ZipConstants and therefore imports a large
+                                // number of irrelevant constants that aren't valid here. Instead,
+                                // only allow these two:
+                                fields = ImmutableSet.of("STORED", "DEFLATED")
+                            }
+
+                            if (fields != null) {
+                                val sorted = ArrayList(fields)
+                                Collections.sort(sorted)
+                                if (reflectionFields != null) {
+                                    val rank = HashMap<String, Int>()
+                                    run {
+                                        var i = 0
+                                        val n = sorted.size
+                                        while (i < n) {
+                                            rank.put(sorted[i], reflectionFields.size + i)
+                                            i++
+
+                                        }
+                                    }
+                                    var i = 0
+                                    val n = reflectionFields.size
+                                    while (i < n) {
+                                        rank.put(reflectionFields[i].name, i)
+                                        i++
+                                    }
+                                    sorted.sortWith(Comparator { o1, o2 ->
+                                        val rank1 = rank[o1]
+                                        val rank2 = rank[o2]
+                                        val delta = rank1!! - rank2!!
+                                        if (delta != 0) {
+                                            return@Comparator delta
+
+                                        }
+                                        o1.compareTo(o2)
+                                    })
+                                }
+                                var first = true
+                                for (field in sorted) {
+                                    if (first) {
+                                        first = false
+                                    } else {
+                                        sb.append(',').append(' ')
+                                    }
+                                    sb.append(clsName).append('.').append(field)
+                                }
+                                found = true
+                            }
+                        }
+                        // Attempt to sort in reflection order
+                        if (!found && reflectionFields != null && (apiFilter == null || apiFilter.hasClass(clsName))) {
+                            // Attempt with reflection
+                            var first = true
+                            for (field in reflectionFields) {
+                                if (field.type == Integer.TYPE || field.type == Int::class.javaPrimitiveType) {
+                                    if (first) {
+                                        first = false
+                                    } else {
+                                        sb.append(',').append(' ')
+                                    }
+                                    sb.append(clsName).append('.').append(field.name)
+                                }
+                            }
+                        }
+                        sb.append('}')
+                        value = sb.toString()
+                        if (sb.length > 2) { // 2: { }
+                            found = true
+                        }
+                    }
+
+                    if (!found) {
+                        return null
+                    }
+                }
+
+                if (apiFilter != null) {
+                    value = removeFiltered(value)
+                    while (value.contains(", ,")) {
+                        value = value.replace(", ,", ",")
+                    }
+                    if (value.startsWith(", ")) {
+                        value = value.substring(2)
+                    }
+                }
+
+                val attributes = mutableListOf<XmlBackedAnnotationAttribute>()
+                attributes.add(XmlBackedAnnotationAttribute(TYPE_DEF_VALUE_ATTRIBUTE, value))
+                if (flag) {
+                    attributes.add(XmlBackedAnnotationAttribute(TYPE_DEF_FLAG_ATTRIBUTE, VALUE_TRUE))
+                }
+                return PsiAnnotationItem.create(
+                    codebase, XmlBackedAnnotationItem(
+                        codebase,
+                        if (valName == "stringValues") STRING_DEF_ANNOTATION else INT_DEF_ANNOTATION, attributes
+                    )
+                )
+            }
+
+            name == STRING_DEF_ANNOTATION ||
+                    name == ANDROID_STRING_DEF ||
+                    name == INT_DEF_ANNOTATION ||
+                    name == ANDROID_INT_DEF -> {
+                val children = getChildren(annotationElement)
+                var valueElement = children[0]
+                val valName = valueElement.getAttribute(ATTR_NAME)
+                assert(TYPE_DEF_VALUE_ATTRIBUTE == valName)
+                val value = valueElement.getAttribute(ATTR_VAL)
+                var flag = false
+                if (children.size == 2) {
+                    valueElement = children[1]
+                    assert(TYPE_DEF_FLAG_ATTRIBUTE == valueElement.getAttribute(ATTR_NAME))
+                    flag = VALUE_TRUE == valueElement.getAttribute(ATTR_VAL)
+                }
+                val intDef = INT_DEF_ANNOTATION == name || ANDROID_INT_DEF == name
+
+                val attributes = mutableListOf<XmlBackedAnnotationAttribute>()
+                attributes.add(XmlBackedAnnotationAttribute(TYPE_DEF_VALUE_ATTRIBUTE, value))
+                if (flag) {
+                    attributes.add(XmlBackedAnnotationAttribute(TYPE_DEF_FLAG_ATTRIBUTE, VALUE_TRUE))
+                }
+                return PsiAnnotationItem.create(
+                    codebase, XmlBackedAnnotationItem(
+                        codebase,
+                        if (intDef) INT_DEF_ANNOTATION else STRING_DEF_ANNOTATION, attributes
+                    )
+                )
+            }
+
+            name == IDEA_CONTRACT -> {
+                val children = getChildren(annotationElement)
+                val valueElement = children[0]
+                val value = valueElement.getAttribute(ATTR_VAL)
+                val pure = valueElement.getAttribute(ATTR_PURE)
+                return if (pure != null && !pure.isEmpty()) {
+                    PsiAnnotationItem.create(
+                        codebase, XmlBackedAnnotationItem(
+                            codebase, name,
+                            listOf(
+                                XmlBackedAnnotationAttribute(TYPE_DEF_VALUE_ATTRIBUTE, value),
+                                XmlBackedAnnotationAttribute(ATTR_PURE, pure)
+                            )
+                        )
+                    )
+                } else {
+                    PsiAnnotationItem.create(
+                        codebase, XmlBackedAnnotationItem(
+                            codebase, name,
+                            listOf(XmlBackedAnnotationAttribute(TYPE_DEF_VALUE_ATTRIBUTE, value))
+                        )
+                    )
+                }
+            }
+
+            isNonNull(name) -> return codebase.createAnnotation("@$SUPPORT_NOTNULL")
+
+            isNullable(name) -> return codebase.createAnnotation("@$SUPPORT_NULLABLE")
+
+            else -> {
+                val children = getChildren(annotationElement)
+                if (children.isEmpty()) {
+                    return codebase.createAnnotation("@$name")
+                }
+                val attributes = mutableListOf<XmlBackedAnnotationAttribute>()
+                for (valueElement in children) {
+                    attributes.add(
+                        XmlBackedAnnotationAttribute(
+                            valueElement.getAttribute(ATTR_NAME) ?: continue,
+                            valueElement.getAttribute(ATTR_VAL) ?: continue
+                        )
+                    )
+                }
+                return PsiAnnotationItem.create(codebase, XmlBackedAnnotationItem(codebase, name, attributes))
+            }
+        }
+    }
+
+    private fun removeFiltered(originalValue: String): String {
+        var value = originalValue
+        assert(apiFilter != null)
+        if (value.startsWith("{")) {
+            value = value.substring(1)
+        }
+        if (value.endsWith("}")) {
+            value = value.substring(0, value.length - 1)
+        }
+        value = value.trim { it <= ' ' }
+        val sb = StringBuilder(value.length)
+        sb.append('{')
+        for (escaped in Splitter.on(',').omitEmptyStrings().trimResults().split(value)) {
+            val fqn = unescapeXml(escaped)
+            if (fqn.startsWith("\"")) {
+                continue
+            }
+            val index = fqn.lastIndexOf('.')
+            val cls = fqn.substring(0, index)
+            val field = fqn.substring(index + 1)
+            if (apiFilter?.hasField(cls, field) != false) {
+                if (sb.length > 1) { // 0: '{'
+                    sb.append(", ")
+                }
+                sb.append(fqn)
+            } else if (listIgnored) {
+                warning("Skipping constant from typedef because it is not part of the SDK: " + fqn)
+            }
+        }
+        sb.append('}')
+        return escapeXml(sb.toString())
+    }
+
+    private fun isNonNull(name: String): Boolean {
+        return name == IDEA_NOTNULL
+                || name == ANDROID_NOTNULL
+                || name == SUPPORT_NOTNULL
+    }
+
+    private fun isNullable(name: String): Boolean {
+        return name == IDEA_NULLABLE
+                || name == ANDROID_NULLABLE
+                || name == SUPPORT_NULLABLE
+    }
+
+    /**
+     * Returns true if this XML entry contains historic metadata, e.g. has
+     * an api attribute which designates that this API may no longer be in the SDK,
+     * but the annotations should be preserved for older API levels
+     */
+    private fun hasHistoricData(@NonNull item: Element): Boolean {
+        var curr: Node? = item.firstChild
+        while (curr != null) {
+            // Example:
+            // <item name="android.provider.Browser BOOKMARKS_URI">
+            //   <annotation name="android.support.annotation.RequiresPermission.Read">
+            //     <val name="value" val="&quot;com.android.browser.permission.READ_HISTORY_BOOKMARKS&quot;" />
+            //     <val name="apis" val="&quot;..22&quot;" />
+            //   </annotation>
+            //   ..
+            if (curr.nodeType == Node.ELEMENT_NODE && "annotation" == curr.nodeName) {
+                var inner: Node? = curr.firstChild
+                while (inner != null) {
+                    if (inner.nodeType == Node.ELEMENT_NODE &&
+                        "val" == inner.nodeName &&
+                        "apis" == (inner as Element).getAttribute("name")
+                    ) {
+                        return true
+                    }
+                    inner = inner.nextSibling
+                }
+            }
+            curr = curr.nextSibling
+        }
+
+        return false
+    }
+
+    @NonNull
+    private fun escapeXml(@NonNull unescaped: String): String {
+        return XmlEscapers.xmlAttributeEscaper().escape(unescaped)
+    }
+
+    @NonNull
+    private fun unescapeXml(@NonNull escaped: String): String {
+        var workingString = escaped.replace(QUOT_ENTITY, "\"")
+        workingString = workingString.replace(LT_ENTITY, "<")
+        workingString = workingString.replace(GT_ENTITY, ">")
+        workingString = workingString.replace(APOS_ENTITY, "'")
+        workingString = workingString.replace(AMP_ENTITY, "&")
+
+        return workingString
+    }
+}
+
+// TODO: Replace with usage of DefaultAnnotationValue?
+data class XmlBackedAnnotationAttribute(
+    override val name: String,
+    private val valueLiteral: String
+) : AnnotationAttribute {
+    override val value: AnnotationAttributeValue = DefaultAnnotationValue.create(valueLiteral)
+
+    override fun toString(): String {
+        return "$name=$valueLiteral"
+    }
+}
+
+// TODO: Replace with usage of DefaultAnnotationAttribute?
+class XmlBackedAnnotationItem(
+    override var codebase: Codebase,
+    private val qualifiedName: String,
+    private val attributes: List<XmlBackedAnnotationAttribute> = emptyList()
+) : AnnotationItem {
+    override fun qualifiedName(): String? = AnnotationItem.mapName(codebase, qualifiedName)
+
+    override fun attributes() = attributes
+
+    override fun toSource(): String {
+        val qualifiedName = qualifiedName() ?: return ""
+
+        if (attributes.isEmpty()) {
+            return "@" + qualifiedName
+        }
+
+        val sb = StringBuilder(30)
+        sb.append("@")
+        sb.append(qualifiedName)
+        sb.append("(")
+        attributes.joinTo(sb)
+        sb.append(")")
+
+        return sb.toString()
+    }
+}
diff --git a/src/main/java/com/android/tools/metalava/ApiAnalyzer.kt b/src/main/java/com/android/tools/metalava/ApiAnalyzer.kt
new file mode 100644
index 0000000..bf36dda
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/ApiAnalyzer.kt
@@ -0,0 +1,1085 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava
+
+import com.android.tools.metalava.doclava1.Errors
+import com.android.tools.metalava.model.AnnotationAttributeValue
+import com.android.tools.metalava.model.ClassItem
+import com.android.tools.metalava.model.Codebase
+import com.android.tools.metalava.model.ConstructorItem
+import com.android.tools.metalava.model.FieldItem
+import com.android.tools.metalava.model.Item
+import com.android.tools.metalava.model.MethodItem
+import com.android.tools.metalava.model.PackageItem
+import com.android.tools.metalava.model.PackageList
+import com.android.tools.metalava.model.ParameterItem
+import com.android.tools.metalava.model.TypeItem
+import com.android.tools.metalava.model.visitors.ApiVisitor
+import com.android.tools.metalava.model.visitors.ItemVisitor
+import com.android.tools.metalava.model.visitors.VisibleItemVisitor
+import java.util.ArrayList
+import java.util.HashMap
+import java.util.HashSet
+import java.util.function.Predicate
+
+/**
+ * The [ApiAnalyzer] is responsible for walking over the various
+ * classes and members and compute visibility etc of the APIs
+ */
+class ApiAnalyzer(
+    /** The code to analyze */
+    private val codebase: Codebase
+) {
+    /** All packages in the API */
+    private val packages: PackageList = codebase.getPackages()
+
+    fun computeApi() {
+        if (codebase.trustedApi()) {
+            // The codebase is already an API; no consistency checks to be performed
+            return
+        }
+
+        // Apply options for packages that should be hidden
+        hidePackages()
+        skipEmitPackages()
+
+        // Propagate visibility down into individual elements -- if a class is hidden,
+        // then the methods and fields are hidden etc
+        propagateHiddenRemovedAndDocOnly(false)
+    }
+
+    fun addConstructors(filter: Predicate<Item>) {
+        // Let's say I have
+        //  class GrandParent { public GrandParent(int) {} }
+        //  class Parent {  Parent(int) {} }
+        //  class Child { public Child(int) {} }
+        //
+        // Here Parent's constructor is not public. For normal stub generation I'd end up with this:
+        //  class GrandParent { public GrandParent(int) {} }
+        //  class Parent { }
+        //  class Child { public Child(int) {} }
+        //
+        // This doesn't compile - Parent can't have a default constructor since there isn't
+        // one for it to invoke on GrandParent.
+        //
+        // I can generate a fake constructor instead, such as
+        //   Parent() { super(0); }
+        //
+        // But it's hard to do this lazily; what if I'm generating the Child class first?
+        // Therefore, we'll instead walk over the hierarchy and insert these constructors
+        // into the Item hierarchy such that code generation can find them.
+        //
+        // (We also need to handle the throws list, so we can't just unconditionally
+        // insert package private constructors
+        //
+        // To do this right I really need to process super constructors before the classes
+        // depending on them.
+
+        // Mark all classes that are the super class of some other class:
+        val allClasses = packages.allClasses().filter { filter.test(it) }
+
+        codebase.clearTags()
+        allClasses.forEach { cls ->
+            cls.superClass()?.tag = true
+        }
+
+        val leafClasses = allClasses.filter { !it.tag }.toList()
+
+        // Now walk through all the leaf classes, and walk up the super hierarchy
+        // and recursively add constructors; we'll do it recursively to make sure that
+        // the superclass has had its constructors initialized first (such that we can
+        // match the parameter lists and throws signatures), and we use the tag fields
+        // to avoid looking at all the internal classes more than once.
+        codebase.clearTags()
+        leafClasses
+            // Filter classes by filter here to not waste time in hidden packages
+            .filter { filter.test(it) }
+            .forEach { addConstructors(it, filter, true) }
+    }
+
+    /**
+     * Handle computing constructor hierarchy. We'll be setting several attributes:
+     * [ClassItem.defaultConstructor] : The default constructor to invoke in this
+     *   class from subclasses. **NOTE**: This constructor may not be part of
+     *   the [ClassItem.constructors] list, e.g. for package private default constructors
+     *   we've inserted (because there were no public constructors or constructors not
+     *   using hidden parameter types.)
+     *
+     *   If we can find a public constructor we'll put that here instead.
+     *
+     * [ConstructorItem.superConstructor] The default constructor to invoke. If set,
+     * use this rather than the [ClassItem.defaultConstructor].
+     *
+     * [ClassItem.hasPrivateConstructor] Set if this class has one or more private
+     * constructors.
+     *
+     * [Item.tag] : mark for avoiding repeated iteration of internal item nodes
+     *
+     *
+     */
+    private fun addConstructors(cls: ClassItem, filter: Predicate<Item>, isLeaf: Boolean) {
+        // What happens if we have
+        //  package foo:
+        //     public class A { public A(int) }
+        //  package bar
+        //     public class B extends A { public B(int) }
+        // If I just try inserting package private constructors here things will NOT work:
+        //  package foo:
+        //     public class A { public A(int); A() {} }
+        //  package bar
+        //     public class B extends A { public B(int); B() }
+        //  because A <() is not accessible from B() -- it's outside the same package.
+        //
+        // So, I'll need to model the real constructors for all the scenarios where that
+        // works.
+        //
+        // The remaining challenge is that there will be some gaps: when I don't have
+        // a default constructor, subclass constructors will have to have an explicit
+        // super(args) call to pick the parent constructor to use. And which one?
+        // It generally doesn't matter; just pick one, but unfortunately, the super
+        // constructor can throw exceptions, and in that case the subclass constructor
+        // must also throw all those constructors (you can't surround a super call
+        // with try/catch.)  Luckily, the source code already needs to do this to
+        // compile, so we can just use the same constructor as the super call.
+        // But there are two cases we have to deal with:
+        //   (1) the constructor doesn't call a super constructor; it calls another
+        //       constructor on this class.
+        //   (2) the super constructor it *does* call isn't available.
+        //
+        // For (1), this means that our stub code generator should be prepared to
+        // handle both super- and this- dispatches; we'll handle this by pointing
+        // it to the constructor to use, and it checks to see if the containing class
+        // for the constructor is the same to decide whether to emit "this" or "super".
+
+        if (cls.tag || !cls.isClass()) { // Don't add constructors to interfaces, enums, annotations, etc
+            return
+        }
+
+        // First handle its super class hierarchy to make sure that we've
+        // already constructed super classes
+        val superClass = cls.filteredSuperclass(filter)
+        superClass?.let { it -> addConstructors(it, filter, false) }
+        cls.tag = true
+
+        if (superClass != null) {
+            val superDefaultConstructor = superClass.defaultConstructor
+            if (superDefaultConstructor != null) {
+                val constructors = cls.constructors()
+                for (constructor in constructors) {
+                    val superConstructor = constructor.superConstructor
+                    if (superConstructor == null ||
+                        (superConstructor.containingClass() != superClass &&
+                                superConstructor.containingClass() != cls)
+                    ) {
+                        constructor.superConstructor = superDefaultConstructor
+                    }
+                }
+            }
+        }
+
+        // Find default constructor, if one doesn't exist
+        if (!isLeaf || cls.hasPrivateConstructor || cls.constructors().isNotEmpty()) {
+            val constructors = cls.constructors()
+            for (constructor in constructors) {
+                if (constructor.parameters().isEmpty() && constructor.isPublic) {
+                    cls.defaultConstructor = constructor
+                    return
+                }
+            }
+
+            if (!constructors.isEmpty()) {
+                // Try to pick the constructor, sorting first by "matches filter", then by
+                // uses available types, then by fewest throwables, then fewest parameters,
+                // then based on order in listfilter.test(cls)
+                val first = constructors.first()
+                val best =
+                    if (constructors.size > 1) {
+                        constructors.foldRight(first) { current, next -> pickBest(current, next, filter) }
+                    } else {
+                        first
+                    }
+
+                if (cls.filteredConstructors(filter).contains(best)) {
+                    cls.defaultConstructor = best
+                    return
+                }
+
+                if (!referencesExcludedType(best, filter)) {
+                    cls.defaultConstructor = best
+                    best.mutableModifiers().setPackagePrivate(true)
+                    best.hidden = false
+                    best.docOnly = false
+                    return
+                }
+            }
+
+            // No constructors, yet somebody extends this (or private constructor): we have to invent one, such that
+            // subclasses can dispatch to it in the stub files etc
+            cls.defaultConstructor = cls.createDefaultConstructor().also {
+                it.mutableModifiers().setPackagePrivate(true)
+                it.hidden = false
+                it.superConstructor = superClass?.defaultConstructor;
+            }
+        }
+    }
+
+    private fun referencesExcludedType(constructor: ConstructorItem, filter: Predicate<Item>): Boolean {
+        // Checks parameter types and throws types
+        for (parameter in constructor.parameters()) {
+            val type = parameter.type()
+            if (type.referencesExcludedType(filter)) {
+                return true
+            }
+        }
+        for (cls in constructor.throwsTypes()) {
+            if (!filter.test(cls)) {
+                return true
+            }
+        }
+
+        return false
+    }
+
+    // TODO: Annotation test: @ParameterName, if present, must be supplied on *all* the arguments!
+    // Warn about @DefaultValue("null"); they probably meant @DefaultNull
+    // Supplying default parameter in override is not allowed!
+
+    private fun pickBest(
+        current: ConstructorItem,
+        next: ConstructorItem,
+        filter: Predicate<Item>
+    ): ConstructorItem {
+        val currentMatchesFilter = filter.test(current)
+        val nextMatchesFilter = filter.test(next)
+        if (currentMatchesFilter != nextMatchesFilter) {
+            return if (currentMatchesFilter) {
+                current
+            } else {
+                next
+            }
+        }
+
+        val currentUsesAvailableTypes = !referencesExcludedType(current, filter)
+        val nextUsesAvailableTypes = !referencesExcludedType(current, filter)
+        if (currentUsesAvailableTypes != nextUsesAvailableTypes) {
+            return if (currentUsesAvailableTypes) {
+                current
+            } else {
+                next
+            }
+        }
+
+        val currentThrowsCount = current.throwsTypes().size
+        val nextThrowsCount = next.throwsTypes().size
+
+        return if (currentThrowsCount < nextThrowsCount) {
+            current
+        } else if (currentThrowsCount > nextThrowsCount) {
+            next
+        } else {
+            val currentParameterCount = current.parameters().size
+            val nextParameterCount = next.parameters().size
+            if (currentParameterCount <= nextParameterCount) {
+                current
+            } else
+                next
+        }
+    }
+
+    fun generateInheritedStubs(filterEmit: Predicate<Item>, filterReference: Predicate<Item>) {
+        packages.allClasses().forEach {
+            if (filterEmit.test(it)) {
+                generateInheritedStubs(it, filterEmit, filterReference)
+            }
+        }
+    }
+
+    private fun generateInheritedStubs(cls: ClassItem, filterEmit: Predicate<Item>, filterReference: Predicate<Item>) {
+        if (!cls.isClass()) return
+        if (cls.superClass() == null) return
+        val superClasses: Sequence<ClassItem> = generateSequence(cls.superClass()) { it.superClass() }
+        val hiddenSuperClasses: Sequence<ClassItem> =
+            superClasses.filter { !filterReference.test(it) && !it.isJavaLangObject() }
+
+        if (hiddenSuperClasses.none()) { // not missing any implementation methods
+            return
+        }
+
+        addInheritedStubsFrom(cls, hiddenSuperClasses, superClasses, filterEmit, filterReference)
+        addInheritedInterfacesFrom(cls, hiddenSuperClasses, filterReference)
+
+    }
+
+    private fun addInheritedInterfacesFrom(
+        cls: ClassItem,
+        hiddenSuperClasses: Sequence<ClassItem>,
+        filterReference: Predicate<Item>
+    ) {
+        var interfaceTypes: MutableList<TypeItem>? = null
+        var interfaceTypeClasses: MutableList<ClassItem>? = null
+        for (hiddenSuperClass in hiddenSuperClasses) {
+            for (hiddenInterface in hiddenSuperClass.interfaceTypes()) {
+                val hiddenInterfaceClass = hiddenInterface.asClass()
+                if (filterReference.test(hiddenInterfaceClass ?: continue)) {
+                    if (interfaceTypes == null) {
+                        interfaceTypes = cls.interfaceTypes().toMutableList()
+                        interfaceTypeClasses =
+                                interfaceTypes.asSequence().map { it.asClass() }.filterNotNull().toMutableList()
+                        if (cls.isInterface()) {
+                            cls.superClass()?.let { interfaceTypeClasses.add(it) }
+                        }
+                        cls.setInterfaceTypes(interfaceTypes)
+                    }
+                    if (interfaceTypeClasses!!.any { it == hiddenInterfaceClass }) {
+                        continue
+                    }
+
+                    interfaceTypeClasses.add(hiddenInterfaceClass)
+
+                    if (hiddenInterfaceClass.hasTypeVariables()) {
+                        val mapping = cls.mapTypeVariables(hiddenSuperClass)
+                        if (mapping.isNotEmpty()) {
+                            val mappedType: TypeItem = hiddenInterface.convertType(mapping, cls)
+                            interfaceTypes.add(mappedType)
+                            continue
+                        }
+                    }
+
+                    interfaceTypes.add(hiddenInterface)
+                }
+            }
+        }
+    }
+
+    private fun addInheritedStubsFrom(
+        cls: ClassItem,
+        hiddenSuperClasses: Sequence<ClassItem>,
+        superClasses: Sequence<ClassItem>,
+        filterEmit: Predicate<Item>, filterReference: Predicate<Item>
+    ) {
+
+        // Also generate stubs for any methods we would have inherited from abstract parents
+        // All methods from super classes that (1) aren't overridden in this class already, and
+        // (2) are overriding some method that is in a public interface accessible from this class.
+        val interfaces: Set<TypeItem> = cls.allInterfaceTypes(filterReference).asSequence().toSet()
+
+        // Note that we can't just call method.superMethods() to and see whether any of their containing
+        // classes are among our target APIs because it's possible that the super class doesn't actually
+        // implement the interface, but still provides a matching signature for the interface.
+        // Instead we'll look through all of our interface methods and look for potential overrides
+        val interfaceNames = mutableMapOf<String, MutableList<MethodItem>>()
+        for (interfaceType in interfaces) {
+            val interfaceClass = interfaceType.asClass() ?: continue
+            for (method in interfaceClass.methods()) {
+                val name = method.name()
+                val list = interfaceNames[name] ?: run {
+                    val list = ArrayList<MethodItem>()
+                    interfaceNames[name] = list
+                    list
+                }
+                list.add(method)
+            }
+        }
+
+        // Also add in any abstract methods from public super classes
+        val publicSuperClasses = superClasses.filter { filterEmit.test(it) && !it.isJavaLangObject() }
+        for (superClass in publicSuperClasses) {
+            for (method in superClass.methods()) {
+                if (!method.modifiers.isAbstract()) {
+                    continue
+                }
+                val name = method.name()
+                val list = interfaceNames[name] ?: run {
+                    val list = ArrayList<MethodItem>()
+                    interfaceNames[name] = list
+                    list
+                }
+                list.add(method)
+            }
+        }
+
+        // Find all methods that are inherited from these classes into our class
+        // (making sure that we don't have duplicates, e.g. a method defined by one
+        // inherited class and then overridden by another closer one).
+        // map from method name to super methods overriding our interfaces
+        val map = HashMap<String, MutableList<MethodItem>>()
+
+        for (superClass in hiddenSuperClasses) {
+            for (method in superClass.methods()) {
+                val modifiers = method.modifiers
+                if (!modifiers.isPrivate() && !modifiers.isAbstract()) {
+                    val name = method.name()
+                    val candidates = interfaceNames[name] ?: continue
+                    val parameterCount = method.parameters().size
+                    for (superMethod in candidates) {
+                        if (parameterCount != superMethod.parameters().count()) {
+                            continue
+                        }
+                        if (method.matches(superMethod)) {
+                            val list = map[name] ?: run {
+                                val newList = ArrayList<MethodItem>()
+                                map[name] = newList
+                                newList
+                            }
+                            list.add(method)
+                            break
+                        }
+                    }
+                }
+            }
+        }
+
+        // Remove any methods that are overriding any of our existing methods
+        for (method in cls.methods()) {
+            val name = method.name()
+            val candidates = map[name] ?: continue
+            val iterator = candidates.listIterator()
+            while (iterator.hasNext()) {
+                val inheritedMethod = iterator.next()
+                if (method.matches(inheritedMethod)) {
+                    iterator.remove()
+                }
+            }
+        }
+
+        // Next remove any overrides among the remaining super methods (e.g. one method from a hidden parent is
+        // overriding another method from a more distant hidden parent).
+        map.values.forEach { methods ->
+            if (methods.size >= 2) {
+                for (candidate in ArrayList(methods)) {
+                    methods.removeAll(candidate.superMethods())
+                }
+            }
+        }
+
+        // We're now left with concrete methods in hidden parents that are implementing methods in public
+        // interfaces that are listed in this class. Create stubs for them:
+        map.values.flatten().forEach {
+            val method = cls.createMethod(it)
+            method.documentation = "// Inlined stub from hidden parent class ${it.containingClass().qualifiedName()}\n" +
+                    method.documentation
+            method.inheritedInterfaceMethod = true
+            cls.addMethod(method)
+        }
+    }
+
+    /** Hide packages explicitly listed in [Options.hidePackages] */
+    private fun hidePackages() {
+        for (pkgName in options.hidePackages) {
+            val pkg = codebase.findPackage(pkgName) ?: continue
+            pkg.hidden = true
+            pkg.included = false // because included has already been initialized
+        }
+    }
+
+    /** Apply emit filters listed in [Options.skipEmitPackages] */
+    private fun skipEmitPackages() {
+        for (pkgName in options.skipEmitPackages) {
+            val pkg = codebase.findPackage(pkgName) ?: continue
+            pkg.emit = false
+        }
+    }
+
+    /** Merge in external data from all configured sources */
+    fun mergeExternalAnnotations() {
+        val mergeAnnotations = options.mergeAnnotations
+        if (!mergeAnnotations.isEmpty()) {
+            AnnotationsMerger(codebase, options.apiFilter).merge(mergeAnnotations)
+        }
+    }
+
+    /**
+     * Propagate the hidden flag down into individual elements -- if a class is hidden, then the methods and fields
+     * are hidden etc
+     */
+    private fun propagateHiddenRemovedAndDocOnly(includingFields: Boolean) {
+        packages.accept(object : ItemVisitor(visitConstructorsAsMethods = true, nestInnerClasses = true) {
+            override fun visitItem(item: Item) {
+                if (item.modifiers.hasShowAnnotation()) {
+                    item.hidden = false
+                } else if (item.modifiers.hasHideAnnotations()) {
+                    item.hidden = true
+                }
+            }
+
+            override fun visitPackage(pkg: PackageItem) {
+                val containingPackage = pkg.containingPackage()
+                if (containingPackage != null) {
+                    if (containingPackage.hidden) {
+                        pkg.hidden = true
+                    }
+                    if (containingPackage.docOnly) {
+                        pkg.docOnly = true
+                    }
+                }
+            }
+
+            override fun visitClass(cls: ClassItem) {
+                val containingClass = cls.containingClass()
+                if (containingClass != null) {
+                    if (containingClass.hidden) {
+                        cls.hidden = true
+                    }
+                    if (containingClass.docOnly) {
+                        cls.docOnly = true
+                    }
+                    if (containingClass.removed) {
+                        cls.removed = true
+                    }
+                } else {
+                    val containingPackage = cls.containingPackage()
+                    if (containingPackage.hidden && !containingPackage.isDefault) {
+                        cls.hidden = true
+                    }
+                    if (containingPackage.docOnly && !containingPackage.isDefault) {
+                        cls.docOnly = true
+                    }
+                    if (containingPackage.removed && !cls.modifiers.hasShowAnnotation()) {
+                        cls.removed = true
+                    }
+                }
+            }
+
+            override fun visitMethod(method: MethodItem) {
+                val containingClass = method.containingClass()
+                if (containingClass.hidden) {
+                    method.hidden = true
+                }
+                if (containingClass.docOnly) {
+                    method.docOnly = true
+                }
+                if (containingClass.removed) {
+                    method.removed = true
+                }
+            }
+
+            override fun visitField(field: FieldItem) {
+                val containingClass = field.containingClass()
+                /* We don't always propagate field visibility down to the fields
+                   because we sometimes move fields around, and in that
+                   case we don't want to carry forward the "hidden" attribute
+                   from the field that wasn't marked on the field but its
+                   container interface.
+                */
+                if (includingFields && containingClass.hidden) {
+                    field.hidden = true
+                }
+                if (containingClass.docOnly) {
+                    field.docOnly = true
+                }
+                if (containingClass.removed) {
+                    field.removed = true
+                }
+            }
+        })
+    }
+
+    private fun applyApiFilter() {
+        options.apiFilter?.let { filter ->
+            packages.accept(object : VisibleItemVisitor() {
+
+                override fun visitPackage(pkg: PackageItem) {
+                    if (!filter.hasPackage(pkg.qualifiedName())) {
+                        pkg.included = false
+                    }
+                }
+
+                override fun visitClass(cls: ClassItem) {
+                    if (!filter.hasClass(cls.qualifiedName())) {
+                        cls.included = false
+                    }
+                }
+
+                override fun visitMethod(method: MethodItem) {
+                    if (!filter.hasMethod(
+                            method.containingClass().qualifiedName(), method.name(),
+                            method.formatParameters()
+                        )
+                    ) {
+                        method.included = false
+                    }
+                }
+
+                override fun visitField(field: FieldItem) {
+                    if (!filter.hasField(field.containingClass().qualifiedName(), field.name())) {
+                        field.included = false
+                    }
+                }
+            })
+        }
+    }
+
+    private fun checkHiddenTypes() {
+        packages.accept(object : ApiVisitor(codebase, visitConstructorsAsMethods = false) {
+            override fun visitMethod(method: MethodItem) {
+                checkType(method, method.returnType() ?: return) // constructors don't have
+            }
+
+            override fun visitField(field: FieldItem) {
+                checkType(field, field.type())
+            }
+
+            override fun visitParameter(parameter: ParameterItem) {
+                checkType(parameter, parameter.type())
+            }
+
+            private fun checkType(item: Item, type: TypeItem) {
+                if (type.primitive) {
+                    return
+                }
+
+                val cls = type.asClass()
+
+                // Don't flag type parameters like T
+                if (cls?.isTypeParameter == true) {
+                    return
+                }
+
+                // class may be null for things like array types and ellipsis types,
+                // but iterating through the type argument classes below will find and
+                // check the component class
+                if (cls != null && !filterReference.test(cls) && !cls.isFromClassPath()) {
+                    reporter.report(
+                        Errors.HIDDEN_TYPE_PARAMETER, item,
+                        "${item.toString().capitalize()} references hidden type $type."
+                    )
+                }
+
+                type.typeArgumentClasses()
+                    .filter { it != cls }
+                    .forEach { checkType(item, it) }
+            }
+
+            private fun checkType(item: Item, cls: ClassItem) {
+                if (!filterReference.test(cls)) {
+                    if (!cls.isFromClassPath()) {
+                        reporter.report(
+                            Errors.HIDDEN_TYPE_PARAMETER, item,
+                            "${item.toString().capitalize()} references hidden type $cls."
+                        )
+                    }
+                } else {
+                    cls.typeArgumentClasses()
+                        .filter { it != cls }
+                        .forEach { checkType(item, it) }
+                }
+            }
+        })
+    }
+
+    private fun ensureSystemServicesProtectedWithPermission() {
+        if (options.showAnnotations.contains("android.annotation.SystemApi") && options.manifest != null) {
+            // Look for Android @SystemApi exposed outside the normal SDK; we require
+            // that they're protected with a system permission.
+
+            packages.accept(object : ApiVisitor(codebase) {
+                override fun visitClass(cls: ClassItem) {
+                    // This class is a system service if it's annotated with @SystemService,
+                    // or if it's android.content.pm.PackageManager
+                    if (cls.modifiers.isAnnotatedWith("android.annotation.SystemService") ||
+                        cls.qualifiedName() == "android.content.pm.PackageManager"
+                    ) {
+                        // Check permissions on system services
+                        for (method in cls.filteredMethods(filterEmit)) {
+                            checkSystemPermissions(method)
+                        }
+                    }
+                }
+            })
+        }
+    }
+
+    private fun checkSystemPermissions(method: MethodItem) {
+        if (method.isImplicitConstructor()) { // Don't warn on non-source elements like implicit default constructors
+            return
+        }
+
+        val annotation = method.modifiers.findAnnotation("android.annotation.RequiresPermission")
+        var hasAnnotation = false
+
+        if (annotation != null) {
+            hasAnnotation = true
+            for (attribute in annotation.attributes()) {
+                var values: List<AnnotationAttributeValue>? = null
+                var any = false
+                when (attribute.name) {
+                    "value", "allOf" -> {
+                        values = attribute.leafValues()
+                    }
+                    "anyOf" -> {
+                        any = true
+                        values = attribute.leafValues()
+                    }
+                }
+
+                values ?: continue
+
+                val system = ArrayList<String>()
+                val nonSystem = ArrayList<String>()
+                val missing = ArrayList<String>()
+                for (value in values) {
+                    val perm = (value.value() ?: value.toSource()).toString()
+                    val level = codebase.getPermissionLevel(perm)
+                    if (level == null) {
+                        if (any) {
+                            missing.add(perm)
+                            continue
+                        }
+
+                        reporter.report(
+                            Errors.REMOVED_FIELD, method,
+                            "Permission '$perm' is not defined by manifest ${codebase.manifest}."
+                        )
+                        continue
+                    }
+                    if (level.contains("normal") || level.contains("dangerous")
+                        || level.contains("ephemeral")
+                    ) {
+                        nonSystem.add(perm)
+                    } else {
+                        system.add(perm)
+                    }
+                }
+                if (any && missing.size == values.size) {
+                    reporter.report(
+                        Errors.REMOVED_FIELD, method,
+                        "None of the permissions ${missing.joinToString()} are defined by manifest " +
+                                "${codebase.manifest}."
+                    )
+                }
+
+                if (system.isEmpty() && nonSystem.isEmpty()) {
+                    hasAnnotation = false
+                } else if (any && !nonSystem.isEmpty() || !any && system.isEmpty()) {
+                    reporter.report(
+                        Errors.REQUIRES_PERMISSION, method, "Method '" + method.name()
+                                + "' must be protected with a system permission; it currently"
+                                + " allows non-system callers holding " + nonSystem.toString()
+                    )
+                }
+            }
+        }
+
+        if (!hasAnnotation) {
+            reporter.report(
+                Errors.REQUIRES_PERMISSION, method, "Method '" + method.name()
+                        + "' must be protected with a system permission."
+            )
+        }
+    }
+
+    fun performChecks() {
+        if (codebase.trustedApi()) {
+            // The codebase is already an API; no consistency checks to be performed
+            return
+        }
+
+        // TODO for performance: Single iteration over the whole API surface!
+        ensureSystemServicesProtectedWithPermission()
+        checkHiddenTypes()
+
+        packages.accept(object : ApiVisitor(codebase) {
+            override fun visitItem(item: Item) {
+                // TODO: Check annotations and also mark removed/hidden based on annotations
+                if (item.deprecated && !item.documentation.contains("@deprecated")) {
+                    reporter.report(
+                        Errors.DEPRECATION_MISMATCH, item,
+                        "${item.toString().capitalize()}: @Deprecated annotation (present) and @deprecated doc tag (not present) do not match"
+                    )
+                }
+                // TODO: Check opposite (doc tag but no annotation)
+                // TODO: Other checks
+            }
+        })
+    }
+
+    fun handleStripping() {
+        // TODO: Switch to visitor iteration
+        //val stubPackages = options.stubPackages
+        val stubImportPackages = options.stubImportPackages
+        handleStripping(stubImportPackages)
+    }
+
+    private fun handleStripping(stubImportPackages: Set<String>) {
+        val notStrippable = HashSet<ClassItem>(5000)
+
+        // If a class is public or protected, not hidden, not imported and marked as included,
+        // then we can't strip it
+        val allTopLevelClasses = codebase.getPackages().allTopLevelClasses().toList()
+        allTopLevelClasses
+            .filter { it.checkLevel() && it.emit && !it.hidden() }
+            .forEach {
+                cantStripThis(it, notStrippable, stubImportPackages)
+            }
+
+        // complain about anything that looks includeable but is not supposed to
+        // be written, e.g. hidden things
+        for (cl in notStrippable) {
+            if (!cl.isHiddenOrRemoved()) {
+                for (m in cl.methods()) {
+                    if (!m.checkLevel()) {
+                        continue
+                    }
+                    if (m.isHiddenOrRemoved()) {
+                        reporter.report(
+                            Errors.UNAVAILABLE_SYMBOL, m,
+                            "Reference to unavailable method " + m.name()
+                        )
+                    } else if (m.deprecated) {
+                        // don't bother reporting deprecated methods
+                        // unless they are public
+                        reporter.report(
+                            Errors.DEPRECATED, m, "Method " + cl.qualifiedName() + "."
+                                    + m.name() + " is deprecated"
+                        )
+                    }
+
+                    val returnType = m.returnType()
+                    var hiddenClass = findHiddenClasses(returnType, stubImportPackages)
+                    if (hiddenClass != null && !hiddenClass.isFromClassPath()) {
+                        if (hiddenClass.qualifiedName() == returnType?.asClass()?.qualifiedName()) {
+                            // Return type is hidden
+                            reporter.report(
+                                Errors.UNAVAILABLE_SYMBOL, m,
+                                "Method ${cl.qualifiedName()}.${m.name()} returns unavailable " +
+                                        "type ${hiddenClass.simpleName()}"
+                            )
+                        } else {
+                            // Return type contains a generic parameter
+                            reporter.report(
+                                Errors.HIDDEN_TYPE_PARAMETER, m,
+                                "Method ${cl.qualifiedName()}.${m.name()} returns unavailable " +
+                                        "type ${hiddenClass.simpleName()} as a type parameter"
+                            )
+                        }
+                    }
+
+                    for (p in m.parameters()) {
+                        val t = p.type()
+                        if (!t.primitive) {
+                            hiddenClass = findHiddenClasses(t, stubImportPackages)
+                            if (hiddenClass != null && !hiddenClass.isFromClassPath()) {
+                                if (hiddenClass.qualifiedName() == t.asClass()?.qualifiedName()) {
+                                    // Parameter type is hidden
+                                    reporter.report(
+                                        Errors.UNAVAILABLE_SYMBOL, m,
+                                        "Parameter of unavailable type $t in ${cl.qualifiedName()}.${m.name()}()"
+                                    )
+                                } else {
+                                    // Parameter type contains a generic parameter
+                                    reporter.report(
+                                        Errors.HIDDEN_TYPE_PARAMETER, m,
+                                        "Parameter uses type parameter of unavailable type $t in ${cl.qualifiedName()}.${m.name()}()"
+                                    )
+                                }
+                            }
+                        }
+                    }
+                }
+            } else if (cl.deprecated) {
+                // not hidden, but deprecated
+                reporter.report(Errors.DEPRECATED, cl, "Class ${cl.qualifiedName()} is deprecated")
+            }
+        }
+    }
+
+    private fun cantStripThis(
+        cl: ClassItem,
+        notStrippable: MutableSet<ClassItem>,
+        stubImportPackages: Set<String>?
+    ) {
+        if (stubImportPackages != null && stubImportPackages.contains(cl.containingPackage().qualifiedName())) {
+            // if the package is imported then it does not need stubbing.
+            return
+        }
+
+        if (cl.isFromClassPath()) {
+            return
+        }
+
+        if (!notStrippable.add(cl)) {
+            // slight optimization: if it already contains cl, it already contains
+            // all of cl's parents
+            return
+        }
+
+        // cant strip any public fields or their generics
+        for (field in cl.fields()) {
+            if (!field.checkLevel()) {
+                continue
+            }
+            val fieldType = field.type()
+            if (!fieldType.primitive) {
+                val typeClass = fieldType.asClass()
+                if (typeClass != null) {
+                    cantStripThis(
+                        typeClass, notStrippable, stubImportPackages
+                    )
+                }
+                for (cls in fieldType.typeArgumentClasses()) {
+                    cantStripThis(
+                        cls, notStrippable, stubImportPackages
+                    )
+                }
+            }
+        }
+        // cant strip any of the type's generics
+        for (cls in cl.typeArgumentClasses()) {
+            cantStripThis(
+                cls, notStrippable, stubImportPackages
+            )
+        }
+        // cant strip any of the annotation elements
+        // cantStripThis(cl.annotationElements(), notStrippable);
+        // take care of methods
+        cantStripThis(cl.methods(), notStrippable, stubImportPackages)
+        cantStripThis(cl.constructors(), notStrippable, stubImportPackages)
+        // blow the outer class open if this is an inner class
+        val containingClass = cl.containingClass()
+        if (containingClass != null) {
+            cantStripThis(
+                containingClass, notStrippable, stubImportPackages
+            )
+        }
+        // blow open super class and interfaces
+        val supr = cl.superClass()
+        if (supr != null) {
+            if (supr.isHiddenOrRemoved()) {
+                // cl is a public class declared as extending a hidden superclass.
+                // this is not a desired practice but it's happened, so we deal
+                // with it by finding the first super class which passes checklevel for purposes of
+                // generating the doc & stub information, and proceeding normally.
+                val publicSuper = cl.publicSuperClass()
+                // TODO: Initialize and pass super type too (in case generics are involved)
+                cl.setSuperClass(publicSuper)
+                if (!supr.isFromClassPath()) {
+                    reporter.report(
+                        Errors.HIDDEN_SUPERCLASS, cl, "Public class " + cl.qualifiedName()
+                                + " stripped of unavailable superclass " + supr.qualifiedName()
+                    )
+                }
+            } else {
+                cantStripThis(
+                    supr, notStrippable, stubImportPackages
+                )
+                if (supr.isPrivate && !supr.isFromClassPath()) {
+                    reporter.report(
+                        Errors.PRIVATE_SUPERCLASS, cl, "Public class "
+                                + cl.qualifiedName() + " extends private class " + supr.qualifiedName()
+                    )
+                }
+            }
+        }
+    }
+
+    private fun cantStripThis(
+        methods: List<MethodItem>, notStrippable: MutableSet<ClassItem>,
+        stubImportPackages: Set<String>?
+    ) {
+        // for each method, blow open the parameters, throws and return types. also blow open their
+        // generics
+        for (method in methods) {
+            if (!method.checkLevel()) {
+                continue
+            }
+            for (typeParameterClass in method.typeArgumentClasses()) {
+                cantStripThis(
+                    typeParameterClass, notStrippable,
+                    stubImportPackages
+                )
+            }
+            for (parameter in method.parameters()) {
+                for (parameterTypeClass in parameter.type().typeArgumentClasses()) {
+                    cantStripThis(
+                        parameterTypeClass, notStrippable, stubImportPackages
+                    )
+                    for (tcl in parameter.type().typeArgumentClasses()) {
+                        if (tcl.isHiddenOrRemoved()) {
+                            reporter.report(
+                                Errors.UNAVAILABLE_SYMBOL, method,
+                                "Parameter of hidden type ${tcl.fullName()}" +
+                                        "in ${method.containingClass().qualifiedName()}.${method.name()}()"
+                            )
+                        } else {
+                            cantStripThis(
+                                tcl, notStrippable,
+                                stubImportPackages
+                            )
+                        }
+                    }
+                }
+            }
+            for (thrown in method.throwsTypes()) {
+                cantStripThis(
+                    thrown, notStrippable, stubImportPackages
+                )
+            }
+            val returnType = method.returnType()
+            if (returnType != null && !returnType.primitive) {
+                val returnTypeClass = returnType.asClass()
+                if (returnTypeClass != null) {
+                    cantStripThis(
+                        returnTypeClass, notStrippable,
+                        stubImportPackages
+                    )
+                    for (tyItem in returnType.typeArgumentClasses()) {
+                        cantStripThis(
+                            tyItem, notStrippable,
+                            stubImportPackages
+                        )
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Find references to hidden classes.
+     *
+     * This finds hidden classes that are used by public parts of the API in order to ensure the
+     * API is self consistent and does not reference classes that are not included in
+     * the stubs. Any such references cause an error to be reported.
+     *
+     * A reference to an imported class is not treated as an error, even though imported classes
+     * are hidden from the stub generation. That is because imported classes are, by definition,
+     * excluded from the set of classes for which stubs are required.
+     *
+     * @param ti the type information to examine for references to hidden classes.
+     * @param stubImportPackages the possibly null set of imported package names.
+     * @return a reference to a hidden class or null if there are none
+     */
+    private fun findHiddenClasses(ti: TypeItem?, stubImportPackages: Set<String>?): ClassItem? {
+        ti ?: return null
+        val ci = ti.asClass() ?: return null
+        return findHiddenClasses(ci, stubImportPackages)
+    }
+
+    private fun findHiddenClasses(ci: ClassItem, stubImportPackages: Set<String>?): ClassItem? {
+        if (stubImportPackages != null && stubImportPackages.contains(ci.containingPackage().qualifiedName())) {
+            return null
+        }
+        if (ci.isHiddenOrRemoved()) return ci
+        for (tii in ci.toType().typeArgumentClasses()) {
+            // Avoid infinite recursion in the case of Foo<T extends Foo>
+            if (tii != ci) {
+                val hiddenClass = findHiddenClasses(tii, stubImportPackages)
+                if (hiddenClass != null) return hiddenClass
+            }
+        }
+        return null
+    }
+}
diff --git a/src/main/java/com/android/tools/metalava/ComparisonVisitor.kt b/src/main/java/com/android/tools/metalava/ComparisonVisitor.kt
new file mode 100644
index 0000000..d906578
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/ComparisonVisitor.kt
@@ -0,0 +1,300 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava
+
+import com.android.tools.metalava.model.AnnotationItem
+import com.android.tools.metalava.model.ClassItem
+import com.android.tools.metalava.model.Codebase
+import com.android.tools.metalava.model.FieldItem
+import com.android.tools.metalava.model.Item
+import com.android.tools.metalava.model.MethodItem
+import com.android.tools.metalava.model.PackageItem
+import com.android.tools.metalava.model.ParameterItem
+import com.android.tools.metalava.model.visitors.ApiVisitor
+import com.intellij.util.containers.Stack
+import java.util.Comparator
+import java.util.function.Predicate
+
+/**
+ * Visitor which visits all items in two matching codebases and
+ * matches up the items and invokes [compare] on each pair, or
+ * [added] or [removed] when items are not matched
+ */
+open class ComparisonVisitor {
+    open fun compare(old: Item, new: Item) {}
+    open fun added(item: Item) {}
+    open fun removed(item: Item) {}
+
+    open fun compare(old: PackageItem, new: PackageItem) {}
+    open fun compare(old: ClassItem, new: ClassItem) {}
+    open fun compare(old: MethodItem, new: MethodItem) {}
+    open fun compare(old: FieldItem, new: FieldItem) {}
+    open fun compare(old: ParameterItem, new: ParameterItem) {}
+
+    open fun added(item: PackageItem) {}
+    open fun added(item: ClassItem) {}
+    open fun added(item: MethodItem) {}
+    open fun added(item: FieldItem) {}
+    open fun added(item: ParameterItem) {}
+
+    open fun removed(item: PackageItem) {}
+    open fun removed(item: ClassItem) {}
+    open fun removed(item: MethodItem) {}
+    open fun removed(item: FieldItem) {}
+    open fun removed(item: ParameterItem) {}
+}
+
+class CodebaseComparator {
+    /**
+     * Visits this codebase and compares it with another codebase, informing the visitors about
+     * the correlations and differences that it finds
+     */
+    fun compare(visitor: ComparisonVisitor, old: Codebase, new: Codebase, filter: Predicate<Item>? = null) {
+        // Algorithm: build up two trees (by nesting level); then visit the
+        // two trees
+        val oldTree = createTree(old, filter)
+        val newTree = createTree(new, filter)
+        compare(visitor, oldTree, newTree)
+    }
+
+    private fun compare(visitor: ComparisonVisitor, oldList: List<ItemTree>, newList: List<ItemTree>) {
+        var index1 = 0
+        var index2 = 0
+        val length1 = oldList.size
+        val length2 = newList.size
+
+        while (true) {
+            if (index1 < length1) {
+                if (index2 < length2) {
+                    // Compare the items
+                    val oldTree = oldList[index1]
+                    val newTree = newList[index2]
+                    val old = oldTree.item()
+                    val new = newTree.item()
+                    val compare = compare(old, new)
+                    when {
+                        compare > 0 -> {
+                            index2++
+                            visitAdded(visitor, new)
+                        }
+                        compare < 0 -> {
+                            index1++
+                            visitRemoved(visitor, old)
+                        }
+                        else -> {
+                            visitCompare(visitor, old, new)
+
+                            // Compare the children (recurse)
+                            compare(visitor, oldTree.children, newTree.children)
+
+                            index1++
+                            index2++
+                        }
+                    }
+
+                } else {
+                    // All the remaining items in oldList have been deleted
+                    while (index1 < length1) {
+                        visitRemoved(visitor, oldList[index1++].item())
+                    }
+                }
+            } else if (index2 < length2) {
+                // All the remaining items in newList have been added
+                while (index2 < length2) {
+                    visitAdded(visitor, newList[index2++].item())
+                }
+            } else {
+                break
+            }
+        }
+    }
+
+    private fun visitAdded(visitor: ComparisonVisitor, item: Item) {
+        visitor.added(item)
+
+        when (item) {
+            is PackageItem -> visitor.added(item)
+            is ClassItem -> visitor.added(item)
+            is MethodItem -> visitor.added(item)
+            is FieldItem -> visitor.added(item)
+            is ParameterItem -> visitor.added(item)
+        }
+    }
+
+    private fun visitRemoved(visitor: ComparisonVisitor, item: Item) {
+        visitor.added(item)
+
+        when (item) {
+            is PackageItem -> visitor.removed(item)
+            is ClassItem -> visitor.removed(item)
+            is MethodItem -> visitor.removed(item)
+            is FieldItem -> visitor.removed(item)
+            is ParameterItem -> visitor.removed(item)
+        }
+    }
+
+    private fun visitCompare(visitor: ComparisonVisitor, old: Item, new: Item) {
+        visitor.compare(old, new)
+
+        when (old) {
+            is PackageItem -> visitor.compare(old, new as PackageItem)
+            is ClassItem -> visitor.compare(old, new as ClassItem)
+            is MethodItem -> visitor.compare(old, new as MethodItem)
+            is FieldItem -> visitor.compare(old, new as FieldItem)
+            is ParameterItem -> visitor.compare(old, new as ParameterItem)
+        }
+    }
+
+    private fun compare(item1: Item, item2: Item): Int = comparator.compare(item1, item2)
+
+    companion object {
+        /** Sorting rank for types */
+        private fun typeRank(item: Item): Int {
+            return when (item) {
+                is PackageItem -> 0
+                is MethodItem -> if (item.isConstructor()) 1 else 2
+                is FieldItem -> 3
+                is ClassItem -> 4
+                is ParameterItem -> 5
+                is AnnotationItem -> 6
+                else -> 7
+            }
+        }
+
+        val comparator: Comparator<Item> = Comparator { item1, item2 ->
+            val typeSort = typeRank(item1) - typeRank(item2)
+            when {
+                typeSort != 0 -> typeSort
+                item1 == item2 -> 0
+                else -> when (item1) {
+                    is PackageItem -> {
+                        item1.qualifiedName().compareTo((item2 as PackageItem).qualifiedName())
+                    }
+                    is ClassItem -> {
+                        item1.qualifiedName().compareTo((item2 as ClassItem).qualifiedName())
+                    }
+                    is MethodItem -> {
+                        val delta = item1.name().compareTo((item2 as MethodItem).name())
+                        // TODO: Sort by signatures/parameters
+                        delta
+                    }
+                    is FieldItem -> {
+                        item1.name().compareTo((item2 as FieldItem).name())
+                    }
+                    is ParameterItem -> {
+                        item1.parameterIndex.compareTo((item2 as ParameterItem).parameterIndex)
+                    }
+                    is AnnotationItem -> {
+                        (item1.qualifiedName() ?: "").compareTo((item2 as AnnotationItem).qualifiedName() ?: "")
+                    }
+                    else -> {
+                        error("Unexpected item type ${item1.javaClass}")
+                    }
+                }
+            }
+        }
+
+        val treeComparator: Comparator<ItemTree> = Comparator { item1, item2 ->
+            comparator.compare(item1.item, item2.item())
+        }
+    }
+
+    private fun ensureSorted(items: MutableList<ItemTree>) {
+        items.sortWith(treeComparator)
+        for (item in items) {
+            ensureSorted(item)
+        }
+    }
+
+    private fun ensureSorted(item: ItemTree) {
+        item.children.sortWith(treeComparator)
+        for (child in item.children) {
+            ensureSorted(child)
+        }
+    }
+
+    private fun createTree(codebase: Codebase, filter: Predicate<Item>? = null): List<ItemTree> {
+        // TODO: Make sure the items are sorted!
+        val stack = Stack<ItemTree>()
+        val root = ItemTree(null)
+        stack.push(root)
+
+        val predicate = filter ?: Predicate { true }
+        // TODO: Skip empty packages
+        codebase.accept(object : ApiVisitor(
+            nestInnerClasses = true,
+            inlineInheritedFields = true,
+            filterEmit = predicate,
+            filterReference = predicate
+        ) {
+            override fun visitItem(item: Item) {
+                val node = ItemTree(item)
+                val parent = stack.peek()
+                parent.children += node
+
+                stack.push(node)
+            }
+
+            override fun afterVisitItem(item: Item) {
+                stack.pop()
+            }
+        })
+
+        ensureSorted(root.children)
+        return root.children
+    }
+
+    data class ItemTree(val item: Item?) : Comparable<ItemTree> {
+        val children: MutableList<ItemTree> = mutableListOf()
+        fun item(): Item = item!! // Only the root note can be null, and this method should never be called on it
+
+        override fun compareTo(other: ItemTree): Int {
+            return comparator.compare(item(), other.item())
+        }
+
+        override fun toString(): String {
+            return item.toString()
+        }
+
+        fun prettyPrint(): String {
+            val sb = StringBuilder(1000)
+            prettyPrint(sb, 0)
+            return sb.toString()
+        }
+
+        private fun prettyPrint(sb: StringBuilder, depth: Int) {
+            for (i in 0 until depth) {
+                sb.append("    ")
+            }
+            sb.append(toString())
+            sb.append('\n')
+            for (child in children) {
+                child.prettyPrint(sb, depth + 1)
+            }
+        }
+
+        companion object {
+            fun prettyPrint(list: List<ItemTree>): String {
+                val sb = StringBuilder(1000)
+                for (child in list) {
+                    child.prettyPrint(sb, 0)
+                }
+                return sb.toString()
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/Compatibility.kt b/src/main/java/com/android/tools/metalava/Compatibility.kt
new file mode 100644
index 0000000..7abc4de
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/Compatibility.kt
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava
+
+const val COMPAT_MODE_BY_DEFAULT = true
+
+/**
+ * The old API generator code had a number of quirks. Initially we want to simulate these
+ * quirks to produce compatible signature files and APIs, but we want to track what these quirks
+ * are and be able to turn them off eventually. This class offers more fine grained control
+ * of these compatibility behaviors such that we can enable/disable them selectively
+ */
+var compatibility: Compatibility = Compatibility()
+
+class Compatibility(
+    /** Whether compatibility is generally on */
+    val compat: Boolean = COMPAT_MODE_BY_DEFAULT
+) {
+
+    /** Whether to inline fields from implemented interfaces into concrete classes */
+    var inlineInterfaceFields: Boolean = compat
+
+    /** In signature files, use "implements" instead of "extends" for the super class of
+     * an interface */
+    var extendsForInterfaceSuperClass: Boolean = compat
+
+    /** In signature files, refer to annotations as an "abstract class" instead of an "@interface"
+     * and implementing this interface: java.lang.annotation.Annotation */
+    var classForAnnotations: Boolean = compat
+
+    /** Add in explicit `valueOf` and `values` methods into annotation classes  */
+    var defaultAnnotationMethods: Boolean = compat
+
+    /** In signature files, refer to enums as "class" instead of "enum" */
+    var classForEnums: Boolean = compat
+
+    /** Whether to use a nonstandard, compatibility modifier order instead of the Java canonical order.
+     * ("deprecated" isn't a real modifier, so in "standard" mode it's listed first, as if it was the
+     * `@Deprecated` annotation before the modifier list */
+    var nonstandardModifierOrder: Boolean = compat
+
+    /** In signature files, skip the native modifier from the modifier lists */
+    var skipNativeModifier: Boolean = nonstandardModifierOrder
+
+    /** In signature files, skip the strictfp modifier from the modifier lists */
+    var skipStrictFpModifier: Boolean = nonstandardModifierOrder
+
+    /** Whether to include instance methods in annotation classes for the annotation properties */
+    var skipAnnotationInstanceMethods: Boolean = compat
+
+    /** Include spaces after commas in type strings */
+    var spacesAfterCommas: Boolean = compat
+
+    /**
+     * In signature files, whether interfaces should also be described as "abstract"
+     */
+    var abstractInInterfaces: Boolean = compat
+
+    /**
+     * In signature files, whether annotation types should also be described as "abstract"
+     */
+    var abstractInAnnotations: Boolean = compat
+
+    /**
+     * In signature files, whether interfaces can be listed as final
+     */
+    var finalInInterfaces: Boolean = compat
+
+    /**
+     * In this signature
+     *        public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
+     *  doclava1 would treat this as "throws Throwable" instead of "throws X". This variable turns on
+     *  this compat behavior.
+     * */
+    var useErasureInThrows: Boolean = compat
+
+    /**
+     * Include a single space in front of package private classes with no other modifiers
+     * (this doesn't align well, but is supported to make the output 100% identical to the
+     * doclava1 format
+     */
+    var extraSpaceForEmptyModifiers: Boolean = compat
+
+    /** Format `Map<K,V>` as `Map<K, V>` */
+    var spaceAfterCommaInTypes: Boolean = compat
+
+    /**
+     * Doclava1 sorts classes/interfaces by class name instead of qualified name
+     */
+    var sortClassesBySimpleName: Boolean = compat
+
+    /**
+     * Doclava1 omits type parameters in interfaces (in signature files, not in stubs)
+     */
+    var omitTypeParametersInInterfaces: Boolean = compat
+
+    /**
+     * Doclava1 sorted the methods like this:
+     *
+     *      public final class RoundingMode extends java.lang.Enum {
+     *          method public static java.math.RoundingMode valueOf(java.lang.String);
+     *          method public static java.math.RoundingMode valueOf(int);
+     *          ...
+     *
+     * Note how the two valueOf methods are out of order. With this compatibility mode,
+     * we try to perform the same sorting.
+     */
+    var sortEnumValueOfMethodFirst: Boolean = compat
+
+    /**
+     * Whether packages should be treated as recursive for documentation. In other words,
+     * if a directory has a `packages.html` file containing a `@hide` comment, then
+     * all "sub" packages (directories below this one) will also inherit the same comment.
+     * Java packages aren't supposed to work that way, but doclava does.
+     */
+    var inheritPackageDocs: Boolean = compat
+
+    /** Force methods named "values" in enums to be marked final. This was done by
+     * doclava1 with this comment:
+     *
+     *     Explicitly coerce 'final' state of Java6-compiled enum values() method,
+     *     to match the Java5-emitted base API description.
+     *
+     **/
+    var forceFinalInEnumValueMethods: Boolean = compat
+
+    /** Whether signature files and stubs should contain annotations */
+    var annotationsInSignatures: Boolean = !compat
+
+    /** Emit errors in the old API diff format */
+    var oldErrorOutputFormat: Boolean = false
+
+    /**
+     * When a public class implementing a public interface inherits the implementation
+     * of a method in that interface from a hidden super class, the method must be
+     * included in the stubs etc (since otherwise subclasses would believe they need
+     * to implement that method and can't just inherit it). However, doclava1 does not
+     * list these methods. This flag controls this compatibility behavior.
+     */
+    var skipInheritedInterfaceMethods: Boolean = compat
+
+    /**
+     * Whether to include parameter names in the signature file
+     */
+    val parameterNames: Boolean = true
+
+    // Other examples: sometimes we sort by qualified name, sometimes by full name
+}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/CompatibilityCheck.kt b/src/main/java/com/android/tools/metalava/CompatibilityCheck.kt
new file mode 100644
index 0000000..d935438
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/CompatibilityCheck.kt
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava
+
+import com.android.tools.metalava.NullnessMigration.Companion.findNullnessAnnotation
+import com.android.tools.metalava.NullnessMigration.Companion.isNullable
+import com.android.tools.metalava.doclava1.Errors
+import com.android.tools.metalava.doclava1.Errors.Error
+import com.android.tools.metalava.model.AnnotationItem
+import com.android.tools.metalava.model.ClassItem
+import com.android.tools.metalava.model.FieldItem
+import com.android.tools.metalava.model.Item
+import com.android.tools.metalava.model.MethodItem
+import com.android.tools.metalava.model.PackageItem
+import com.android.tools.metalava.model.ParameterItem
+
+/**
+ * Compares the current API with a previous version and makes sure
+ * the changes are compatible. For example, you can make a previously
+ * nullable parameter non null, but not vice versa.
+ *
+ * TODO: Only allow nullness changes on final classes!
+ */
+class CompatibilityCheck : ComparisonVisitor() {
+    override fun compare(old: Item, new: Item) {
+        // Should not remove nullness information
+        // Can't change information incompatibly
+        val oldNullnessAnnotation = findNullnessAnnotation(old)
+        if (oldNullnessAnnotation != null) {
+            val newNullnessAnnotation = findNullnessAnnotation(new)
+            if (newNullnessAnnotation == null) {
+                val name = AnnotationItem.simpleName(oldNullnessAnnotation)
+                reporter.report(
+                    Errors.INVALID_NULL_CONVERSION, new,
+                    "Attempted to remove $name annotation from ${describe(new)}"
+                )
+            } else {
+                val oldNullable = isNullable(old)
+                val newNullable = isNullable(new)
+                if (oldNullable != newNullable) {
+                    // You can change a parameter from nonnull to nullable
+                    // You can change a method from nullable to nonnull
+                    // You cannot change a parameter from nullable to nonnull
+                    // You cannot change a method from nonnull to nullable
+                    if (oldNullable && old is ParameterItem) {
+                        reporter.report(
+                            Errors.INVALID_NULL_CONVERSION, new,
+                            "Attempted to change parameter from @Nullable to @NonNull: " +
+                                    "incompatible change for ${describe(new)}"
+                        )
+                    } else if (!oldNullable && old is MethodItem) {
+                        reporter.report(
+                            Errors.INVALID_NULL_CONVERSION, new,
+                            "Attempted to change method return from @NonNull to @Nullable: " +
+                                    "incompatible change for ${describe(new)}"
+                        )
+                    }
+                }
+            }
+        }
+    }
+
+    override fun compare(old: ParameterItem, new: ParameterItem) {
+        val prevName = old.publicName() ?: return
+        val newName = new.publicName()
+        if (newName == null) {
+            reporter.report(
+                Errors.PARAMETER_NAME_CHANGE, new,
+                "Attempted to remove parameter name from ${describe(new)} in ${describe(new.containingMethod())}"
+            )
+        } else if (newName != prevName) {
+            reporter.report(
+                Errors.PARAMETER_NAME_CHANGE, new,
+                "Attempted to change parameter name from $prevName to $newName in ${describe(new.containingMethod())}"
+            )
+        }
+    }
+
+    override fun compare(old: ClassItem, new: ClassItem) {
+        if (old.isInterface() != new.isInterface()) {
+            reporter.report(
+                Errors.CHANGED_CLASS, new, "Class " + new.qualifiedName()
+                        + " changed class/interface declaration"
+            )
+        }
+    }
+
+    private fun handleAdded(error: Error, item: Item) {
+        if (item is MethodItem) {
+            // *Overriding* methods from super classes that are outside the
+            // API is OK (e.g. overriding toString() from java.lang.Object)
+            val superMethods = item.superMethods()
+            for (superMethod in superMethods) {
+                if (superMethod.isFromClassPath()) {
+                    return
+                }
+            }
+        }
+
+        reporter.report(error, item, "Added ${describe(item)}")
+    }
+
+    private fun handleRemoved(error: Error, item: Item) {
+        reporter.report(error, item, "Removed ${if (item.deprecated) "deprecated " else ""}${describe(item)}")
+    }
+
+    override fun added(item: PackageItem) {
+        handleAdded(Errors.ADDED_PACKAGE, item)
+    }
+
+    override fun added(item: ClassItem) {
+        val error = if (item.isInterface()) {
+            Errors.ADDED_INTERFACE
+        } else {
+            Errors.ADDED_CLASS
+        }
+        handleAdded(error, item)
+    }
+
+    override fun added(item: MethodItem) {
+        handleAdded(Errors.ADDED_METHOD, item)
+    }
+
+    override fun added(item: FieldItem) {
+        handleAdded(Errors.ADDED_FIELD, item)
+    }
+
+    override fun removed(item: PackageItem) {
+        handleRemoved(Errors.REMOVED_PACKAGE, item)
+    }
+
+    override fun removed(item: ClassItem) {
+        val error = when {
+            item.isInterface() -> Errors.REMOVED_INTERFACE
+            item.deprecated -> Errors.REMOVED_DEPRECATED_CLASS
+            else -> Errors.REMOVED_CLASS
+        }
+        handleRemoved(error, item)
+    }
+
+    override fun removed(item: MethodItem) {
+        handleRemoved(Errors.REMOVED_METHOD, item)
+    }
+
+    override fun removed(item: FieldItem) {
+        handleRemoved(Errors.REMOVED_FIELD, item)
+    }
+
+    private fun describe(item: Item): String {
+        return when (item) {
+            is PackageItem -> "package ${item.qualifiedName()}"
+            is ClassItem -> "class ${item.qualifiedName()}"
+            is FieldItem -> "field ${item.containingClass().qualifiedName()}.${item.name()}"
+            is MethodItem -> "method ${item.containingClass().qualifiedName()}.${item.name()}"
+            is ParameterItem -> "parameter ${item.name()} in " +
+                    "${item.containingMethod().containingClass().qualifiedName()}.${item.containingMethod().name()}"
+            else -> item.toString()
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/DexApiWriter.kt b/src/main/java/com/android/tools/metalava/DexApiWriter.kt
new file mode 100644
index 0000000..15b4573
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/DexApiWriter.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava
+
+import com.android.tools.metalava.model.ClassItem
+import com.android.tools.metalava.model.FieldItem
+import com.android.tools.metalava.model.Item
+import com.android.tools.metalava.model.MethodItem
+import com.android.tools.metalava.model.visitors.ApiVisitor
+import java.io.PrintWriter
+import java.util.function.Predicate
+
+class DexApiWriter(
+    private val writer: PrintWriter,
+    filterEmit: Predicate<Item>,
+    filterReference: Predicate<Item>
+) : ApiVisitor(
+    visitConstructorsAsMethods = true,
+    nestInnerClasses = false,
+    inlineInheritedFields = true,
+    filterEmit = filterEmit,
+    filterReference = filterReference
+) {
+    override fun visitClass(cls: ClassItem) {
+        if (filterEmit.test(cls)) {
+            writer.print(cls.toType().internalName())
+            writer.print("\n")
+        }
+    }
+
+    override fun visitMethod(method: MethodItem) {
+        writer.print(method.containingClass().toType().internalName())
+        writer.print("->")
+        writer.print(method.internalName())
+        writer.print("(")
+        for (pi in method.parameters()) {
+            writer.print(pi.type().internalName())
+        }
+        writer.print(")")
+        if (method.isConstructor()) {
+            writer.print("V")
+        } else {
+            val returnType = method.returnType()
+            writer.print(returnType?.internalName() ?: "V")
+        }
+        writer.print("\n")
+    }
+
+    override fun visitField(field: FieldItem) {
+        val cls = field.containingClass()
+
+        writer.print(cls.toType().internalName())
+        writer.print("->")
+        writer.print(field.name())
+        writer.print(":")
+        writer.print(field.type().internalName())
+        writer.print("\n")
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/DocAnalyzer.kt b/src/main/java/com/android/tools/metalava/DocAnalyzer.kt
new file mode 100644
index 0000000..a70be9f
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/DocAnalyzer.kt
@@ -0,0 +1,521 @@
+package com.android.tools.metalava
+
+import com.android.tools.lint.LintCliClient
+import com.android.tools.lint.checks.ApiLookup
+import com.android.tools.lint.helpers.DefaultJavaEvaluator
+import com.android.tools.metalava.doclava1.Errors
+import com.android.tools.metalava.model.AnnotationAttributeValue
+import com.android.tools.metalava.model.AnnotationItem
+import com.android.tools.metalava.model.ClassItem
+import com.android.tools.metalava.model.Codebase
+import com.android.tools.metalava.model.FieldItem
+import com.android.tools.metalava.model.Item
+import com.android.tools.metalava.model.MemberItem
+import com.android.tools.metalava.model.MethodItem
+import com.android.tools.metalava.model.ParameterItem
+import com.android.tools.metalava.model.visitors.ApiVisitor
+import com.android.tools.metalava.model.visitors.VisibleItemVisitor
+import com.intellij.psi.PsiClass
+import com.intellij.psi.PsiField
+import com.intellij.psi.PsiMethod
+import java.io.File
+import java.util.HashMap
+import java.util.regex.Pattern
+
+/**
+ * Walk over the API and apply tweaks to the documentation, such as
+ *     - Looking for annotations and converting them to auxiliary tags
+ *       that will be processed by the documentation tools later.
+ *     - Reading lint's API database and inserting metadata into
+ *       the documentation like api levels and deprecation levels.
+ *     - Transferring docs from hidden super methods.
+ *     - Performing tweaks for common documentation mistakes, such as
+ *       ending the first sentence with ", e.g. " where javadoc will sadly
+ *       see the ". " and think "aha, that's the end of the sentence!"
+ *       (It works around this by replacing the space with &nbsp;.)
+ *       This will also attempt to fix common typos (Andriod->Android etc).
+ */
+class DocAnalyzer(
+    /** The codebase to analyze */
+    private val codebase: Codebase
+) {
+
+    /** Computes the visible part of the API from all the available code in the codebase */
+    fun enhance() {
+        // Apply options for packages that should be hidden
+        documentsFromAnnotations()
+
+        tweakGrammar()
+
+        // TODO:
+        // addMinSdkVersionMetadata()
+        // addDeprecationMetadata()
+        // insertMissingDocFromHiddenSuperclasses()
+    }
+
+    val mentionsNull: Pattern = Pattern.compile("\\bnull\\b")
+
+    /** Hide packages explicitly listed in [Options.hidePackages] */
+    private fun documentsFromAnnotations() {
+        // Note: Doclava1 inserts its own javadoc parameters into the documentation,
+        // which is then later processed by javadoc to insert actual descriptions.
+        // This indirection makes the actual descriptions of the annotations more
+        // configurable from a separate file -- but since this tool isn't hooked
+        // into javadoc anymore (and is going to be used by for example Dokka too)
+        // instead metalava will generate the descriptions directly in-line into the
+        // docs.
+        //
+        // This does mean that you have to update the metalava source code to update
+        // the docs -- but on the other hand all the other docs in the documentation
+        // set also requires updating framework source code, so this doesn't seem
+        // like an unreasonable burden.
+
+        codebase.accept(object : ApiVisitor(codebase) {
+            override fun visitItem(item: Item) {
+                val annotations = item.modifiers.annotations()
+                if (annotations.isEmpty()) {
+                    return
+                }
+
+                for (annotation in annotations) {
+                    handleAnnotation(annotation, item, depth = 0)
+                }
+
+                /* Handled via @memberDoc/@classDoc on the annotations themselves right now.
+                   That doesn't handle combinations of multiple thread annotations, but those
+                   don't occur yet, right?
+                // Threading annotations: can't look at them one at a time; need to list them
+                // all together
+                if (item is ClassItem || item is MethodItem) {
+                    val threads = findThreadAnnotations(annotations)
+                    threads?.let {
+                        val threadList = it.joinToString(separator = " or ") +
+                                (if (it.size == 1) " thread" else " threads")
+                        val doc = if (item is ClassItem) {
+                            "All methods in this class must be invoked on the $threadList, unless otherwise noted"
+                        } else {
+                            assert(item is MethodItem)
+                            "This method must be invoked on the $threadList"
+                        }
+                        appendDocumentation(doc, item, false)
+                    }
+                }
+                */
+                if (findThreadAnnotations(annotations).size > 1) {
+                    reporter.warning(
+                        item, "Found more than one threading annotation on $item; " +
+                                "the auto-doc feature does not handle this correctly",
+                        Errors.MULTIPLE_THREAD_ANNOTATIONS
+                    )
+                }
+            }
+
+            private fun findThreadAnnotations(annotations: List<AnnotationItem>): List<String> {
+                var result: MutableList<String>? = null
+                for (annotation in annotations) {
+                    val name = annotation.qualifiedName()
+                    if (name != null && name.endsWith("Thread") && name.startsWith("android.support.annotation.")) {
+                        if (result == null) {
+                            result = mutableListOf()
+                        }
+                        val threadName = if (name.endsWith("UiThread")) {
+                            "UI"
+                        } else {
+                            name.substring(name.lastIndexOf('.') + 1, name.length - "Thread".length)
+                        }
+                        result.add(threadName)
+                    }
+                }
+                return result ?: emptyList()
+            }
+
+            /** Fallback if field can't be resolved or if an inlined string value is used */
+            private fun findPermissionField(codebase: Codebase, value: Any): FieldItem? {
+                val perm = value.toString()
+                val permClass = codebase.findClass("android.Manifest.permission")
+                permClass?.fields()?.filter {
+                    it.initialValue(requireConstant = false)?.toString() == perm
+                }?.forEach { return it }
+                return null
+            }
+
+            private fun handleAnnotation(
+                annotation: AnnotationItem,
+                item: Item, depth: Int
+            ) {
+                val name = annotation.qualifiedName()
+                if (name == null || name.startsWith("java.lang.")) {
+                    // Ignore java.lang.Retention etc.
+                    return
+                }
+
+                // Some annotations include the documentation they want inlined into usage docs.
+                // Copy those here:
+
+                if (annotation.isNullable() || annotation.isNonNull()) {
+                    // Some docs already specifically talk about null policy; in that case,
+                    // don't include the docs (since it may conflict with more specific conditions
+                    // outlined in the docs).
+                    if (item.documentation.contains("null") &&
+                        mentionsNull.matcher(item.documentation).find()
+                    ) {
+                        return
+                    }
+                }
+
+                when (item) {
+                    is FieldItem -> {
+                        addDoc(annotation, "memberDoc", item)
+                    }
+                    is MethodItem -> {
+                        addDoc(annotation, "memberDoc", item)
+                        addDoc(annotation, "returnDoc", item)
+                    }
+                    is ParameterItem -> {
+                        addDoc(annotation, "paramDoc", item)
+                    }
+                    is ClassItem -> {
+                        addDoc(annotation, "classDoc", item)
+                    }
+                }
+
+                // Document required permissions
+                if (item is MemberItem && name == "android.support.annotation.RequiresPermission") {
+                    for (attribute in annotation.attributes()) {
+                        var values: List<AnnotationAttributeValue>? = null
+                        var any = false
+                        when (attribute.name) {
+                            "value", "allOf" -> {
+                                values = attribute.leafValues()
+                            }
+                            "anyOf" -> {
+                                any = true
+                                values = attribute.leafValues()
+                            }
+                        }
+
+                        if (values == null || values.isEmpty()) {
+                            continue
+                        }
+
+                        // Look at macros_override.cs for the usage of these
+                        // tags. In particular, search for def:dump_permission
+
+                        val sb = StringBuilder(100)
+                        sb.append("Requires ")
+                        var first = true
+                        for (value in values) {
+                            when {
+                                first -> first = false
+                                any -> sb.append(" or ")
+                                else -> sb.append(" and ")
+                            }
+
+                            val resolved = value.resolve()
+                            val field = if (resolved is FieldItem)
+                                resolved
+                            else {
+                                val v: Any = value.value() ?: value.toSource()
+                                findPermissionField(codebase, v)
+                            }
+                            if (field == null) {
+                                reporter.report(
+                                    Errors.MISSING_PERMISSION, item,
+                                    "Cannot find permission field for $value required by $item (may be hidden or removed)"
+                                )
+                                //return
+                                sb.append(value.toSource())
+
+                            } else {
+                                if (field.isHiddenOrRemoved()) {
+                                    reporter.report(
+                                        Errors.MISSING_PERMISSION, item,
+                                        "Permission $value required by $item is hidden or removed"
+                                    )
+                                }
+                                sb.append("{@link ${field.containingClass().qualifiedName()}#${field.name()}}")
+                            }
+                        }
+
+                        appendDocumentation(sb.toString(), item, false)
+                    }
+                }
+
+                // Document value ranges
+                if (name == "android.support.annotation.IntRange" || name == "android.support.annotation.FloatRange") {
+                    val from: String? = annotation.findAttribute("from")?.value?.toSource()
+                    val to: String? = annotation.findAttribute("to")?.value?.toSource()
+                    // TODO: inclusive/exclusive attributes on FloatRange!
+                    if (from != null || to != null) {
+                        val args = HashMap<String, String>()
+                        if (from != null) args.put("from", from)
+                        if (from != null) args.put("from", from)
+                        if (to != null) args.put("to", to)
+                        val doc = if (from != null && to != null) {
+                            "Value is between $from and $to inclusive"
+                        } else if (from != null) {
+                            "Value is $from or greater"
+                        } else if (to != null) {
+                            "Value is $to or less"
+                        } else {
+                            null
+                        }
+                        appendDocumentation(doc, item, true)
+                    }
+                }
+
+                // Document expected constants
+                if (name == "android.support.annotation.IntDef" || name == "android.support.annotation.LongDef"
+                    || name == "android.support.annotation.StringDef"
+                ) {
+                    val values = annotation.findAttribute("value")?.leafValues() ?: return
+                    val flag = annotation.findAttribute("flag")?.value?.toSource() == "true"
+
+                    // Look at macros_override.cs for the usage of these
+                    // tags. In particular, search for def:dump_int_def
+
+                    val sb = StringBuilder(100)
+                    sb.append("Value is ")
+                    if (flag) {
+                        sb.append("either <code>0</code> or ")
+                        if (values.size > 1) {
+                            sb.append("a combination of ")
+                        }
+                    }
+
+                    values.forEachIndexed { index, value ->
+                        sb.append(
+                            when (index) {
+                                0 -> {
+                                    ""
+                                }
+                                values.size - 1 -> {
+                                    if (flag) {
+                                        ", and "
+                                    } else {
+                                        ", or "
+                                    }
+                                }
+                                else -> {
+                                    ", "
+                                }
+                            }
+                        )
+
+                        val field = value.resolve()
+                        if (field is FieldItem)
+                            sb.append("{@link ${field.containingClass().qualifiedName()}#${field.name()}}")
+                        else {
+                            sb.append(value.toSource())
+                        }
+                    }
+                    appendDocumentation(sb.toString(), item, true)
+                }
+
+                // Thread annotations are ignored here because they're handled as a group afterwards
+
+                // TODO: Resource type annotations
+
+                // Handle inner annotations
+                annotation.resolve()?.modifiers?.annotations()?.forEach { nested ->
+                    if (depth == 20) { // Temp debugging
+                        throw StackOverflowError(
+                            "Unbounded recursion, processing annotation " +
+                                    "${annotation.toSource()} in $item in ${item.compilationUnit()} "
+                        )
+                    }
+                    handleAnnotation(nested, item, depth + 1)
+                }
+            }
+        })
+    }
+
+    /**
+     * Appends the given documentation to the given item.
+     * If it's documentation on a parameter, it is redirected to the surrounding method's
+     * documentation.
+     *
+     * If the [returnValue] flag is true, the documentation is added to the description text
+     * of the method, otherwise, it is added to the return tag. This lets for example
+     * a threading annotation requirement be listed as part of a method description's
+     * text, and a range annotation be listed as part of the return value description.
+     * */
+    private fun appendDocumentation(doc: String?, item: Item, returnValue: Boolean) {
+        doc ?: return
+
+        when (item) {
+            is ParameterItem -> item.containingMethod().appendDocumentation(doc, item.name())
+            is MethodItem ->
+                // Document as part of return annotation, not member doc
+                item.appendDocumentation(doc, if (returnValue) "@return" else null)
+            else -> item.appendDocumentation(doc)
+        }
+    }
+
+    private fun addDoc(annotation: AnnotationItem, tag: String, item: Item) {
+        // TODO: Cache: we shouldn't have to keep looking this up over and over
+        // for example for the nullable/non-nullable annotation classes that
+        // are used everywhere!
+        val cls = annotation.resolve() ?: return
+
+        val documentation = cls.findTagDocumentation(tag)
+        if (documentation != null) {
+            assert(documentation.startsWith("@$tag"), { documentation })
+            // TODO: Insert it in the right place (@return or @param)
+            val section = when {
+                documentation.startsWith("@returnDoc") -> "@return"
+                documentation.startsWith("@paramDoc") -> "@param"
+                documentation.startsWith("@memberDoc") -> null
+                else -> null
+            }
+            val insert = stripMetaTags(documentation.substring(tag.length + 2))
+            item.appendDocumentation(insert, section) // 2: @ and space after tag
+        }
+    }
+
+    private fun stripMetaTags(string: String): String {
+        // Get rid of @hide and @remove tags etc that are part of documentation snippets
+        // we pull in, such that we don't accidentally start applying this to the
+        // item that is pulling in the documentation.
+        if (string.contains("@hide") || string.contains("@remove")) {
+            return string.replace("@hide", "").replace("@remove", "")
+        }
+        return string
+    }
+
+    /** Replacements to perform in documentation */
+    val typos = mapOf(
+        "Andriod" to "Android",
+        "Kitkat" to "KitKat",
+        "LemonMeringuePie" to "Lollipop",
+        "LMP" to "Lollipop",
+        "KeyLimePie" to "KitKat",
+        "KLP" to "KitKat"
+    )
+
+    private fun tweakGrammar() {
+        codebase.accept(object : VisibleItemVisitor() {
+            override fun visitItem(item: Item) {
+                var doc = item.documentation
+                if (doc.isBlank()) {
+                    return
+                }
+
+                for (typo in typos.keys) {
+                    if (doc.contains(typo)) {
+                        val replacement = typos[typo] ?: continue
+                        reporter.report(
+                            Errors.TYPO,
+                            item,
+                            "Replaced $typo with $replacement in documentation for $item"
+                        )
+                        doc = doc.replace(typo, replacement, false)
+                        item.documentation = doc
+                    }
+                }
+
+                val firstDot = doc.indexOf(".")
+                if (firstDot > 0 && doc.regionMatches(firstDot - 1, "e.g. ", 0, 5, false)) {
+                    doc = doc.substring(0, firstDot) + ".g.&nbsp;" + doc.substring(firstDot + 4)
+                    item.documentation = doc
+                }
+            }
+        })
+    }
+
+    fun applyApiLevels(applyApiLevelsXml: File) {
+        val client = object : LintCliClient() {
+            override fun findResource(relativePath: String): File? {
+                if (relativePath == ApiLookup.XML_FILE_PATH) {
+                    return applyApiLevelsXml
+                }
+                return super.findResource(relativePath)
+            }
+
+            override fun getCacheDir(name: String?, create: Boolean): File? {
+                val dir = File(System.getProperty("java.io.tmpdir"))
+                if (create) {
+                    dir.mkdirs()
+                }
+                return dir
+            }
+        }
+
+        val apiLookup = ApiLookup.get(client)
+
+        //codebase.accept(object : VisibleItemVisitor(visitConstructorsAsMethods = false) {
+        codebase.accept(object : ApiVisitor(codebase, visitConstructorsAsMethods = false) {
+            override fun visitMethod(method: MethodItem) {
+                val psiMethod = method.psi() as PsiMethod
+                addApiLevelDocumentation(apiLookup.getMethodVersion(psiMethod), method)
+                addDeprecatedDocumentation(apiLookup.getMethodDeprecatedIn(psiMethod), method)
+            }
+
+            override fun visitClass(cls: ClassItem) {
+                val psiClass = cls.psi() as PsiClass
+                addApiLevelDocumentation(apiLookup.getClassVersion(psiClass), cls)
+                addDeprecatedDocumentation(apiLookup.getClassDeprecatedIn(psiClass), cls)
+            }
+
+            override fun visitField(field: FieldItem) {
+                val psiField = field.psi() as PsiField
+                addApiLevelDocumentation(apiLookup.getFieldVersion(psiField), field)
+                addDeprecatedDocumentation(apiLookup.getFieldDeprecatedIn(psiField), field)
+            }
+
+            private fun addApiLevelDocumentation(level: Int, item: Item) {
+                if (level > 1) {
+                    appendDocumentation("Requires API level $level", item, false)
+                }
+            }
+
+            private fun addDeprecatedDocumentation(level: Int, item: Item) {
+                if (level > 1) {
+                    // TODO: *pre*pend instead!
+                    //val description = "This class was deprecated in API level $level. "
+                    val description =
+                        "<p class=\"caution\"><strong>This class was deprecated in API level 21.</strong></p>"
+                    item.appendDocumentation(description, "@deprecated", append = false)
+                }
+            }
+        })
+    }
+}
+
+fun ApiLookup.getClassVersion(cls: PsiClass): Int {
+    val owner = cls.qualifiedName ?: return -1
+    return getClassVersion(owner)
+}
+
+fun ApiLookup.getMethodVersion(method: PsiMethod): Int {
+    val containingClass = method.containingClass ?: return -1
+    val owner = containingClass.qualifiedName ?: return -1
+    val evaluator = DefaultJavaEvaluator(null, null)
+    val desc = evaluator.getMethodDescription(method, false, false)
+    return getMethodVersion(owner, method.name, desc)
+}
+
+fun ApiLookup.getFieldVersion(field: PsiField): Int {
+    val containingClass = field.containingClass ?: return -1
+    val owner = containingClass.qualifiedName ?: return -1
+    return getFieldVersion(owner, field.name)
+}
+
+fun ApiLookup.getClassDeprecatedIn(cls: PsiClass): Int {
+    val owner = cls.qualifiedName ?: return -1
+    return getClassDeprecatedIn(owner)
+}
+
+fun ApiLookup.getMethodDeprecatedIn(method: PsiMethod): Int {
+    val containingClass = method.containingClass ?: return -1
+    val owner = containingClass.qualifiedName ?: return -1
+    val evaluator = DefaultJavaEvaluator(null, null)
+    val desc = evaluator.getMethodDescription(method, false, false)
+    return getMethodDeprecatedIn(owner, method.name, desc)
+}
+
+fun ApiLookup.getFieldDeprecatedIn(field: PsiField): Int {
+    val containingClass = field.containingClass ?: return -1
+    val owner = containingClass.qualifiedName ?: return -1
+    return getFieldDeprecatedIn(owner, field.name)
+}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/Driver.kt b/src/main/java/com/android/tools/metalava/Driver.kt
new file mode 100644
index 0000000..82c523d
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/Driver.kt
@@ -0,0 +1,686 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+@file:JvmName("Driver")
+
+package com.android.tools.metalava
+
+import com.android.SdkConstants
+import com.android.SdkConstants.DOT_JAVA
+import com.android.SdkConstants.DOT_KT
+import com.android.tools.lint.KotlinLintAnalyzerFacade
+import com.android.tools.lint.LintCoreApplicationEnvironment
+import com.android.tools.lint.LintCoreProjectEnvironment
+import com.android.tools.lint.annotations.Extractor
+import com.android.tools.metalava.apilevels.ApiGenerator
+import com.android.tools.metalava.doclava1.ApiFile
+import com.android.tools.metalava.doclava1.ApiParseException
+import com.android.tools.metalava.doclava1.ApiPredicate
+import com.android.tools.metalava.doclava1.ElidingPredicate
+import com.android.tools.metalava.doclava1.Errors
+import com.android.tools.metalava.doclava1.FilterPredicate
+import com.android.tools.metalava.model.Codebase
+import com.android.tools.metalava.model.Item
+import com.android.tools.metalava.model.psi.PsiBasedCodebase
+import com.android.tools.metalava.model.visitors.ApiVisitor
+import com.google.common.base.Stopwatch
+import com.google.common.collect.Lists
+import com.google.common.io.Files
+import com.intellij.openapi.util.Disposer
+import com.intellij.psi.PsiClassOwner
+import java.io.File
+import java.io.IOException
+import java.io.OutputStreamWriter
+import java.io.PrintWriter
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.TimeUnit.SECONDS
+import java.util.function.Predicate
+import java.util.regex.Pattern
+import kotlin.text.Charsets.UTF_8
+
+const val PROGRAM_NAME = "metalava"
+const val HELP_PROLOGUE = "$PROGRAM_NAME extracts metadata from source code to generate artifacts such as the " +
+        "signature files, the SDK stub files, external annotations etc."
+
+@Suppress("PropertyName") // Can't mark const because trimIndent() :-(
+val BANNER: String = """
+                _        _
+ _ __ ___   ___| |_ __ _| | __ ___   ____ _
+| '_ ` _ \ / _ \ __/ _` | |/ _` \ \ / / _` |
+| | | | | |  __/ || (_| | | (_| |\ V / (_| |
+|_| |_| |_|\___|\__\__,_|_|\__,_| \_/ \__,_|
+""".trimIndent()
+
+fun main(args: Array<String>) {
+    run(args, setExitCode = true)
+}
+
+/**
+ * The metadata driver is a command line interface to extracting various metadata
+ * from a source tree (or existing signature files etc). Run with --help to see
+ * more details.
+ */
+fun run(
+    args: Array<String>,
+    stdout: PrintWriter = PrintWriter(OutputStreamWriter(System.out)),
+    stderr: PrintWriter = PrintWriter(OutputStreamWriter(System.err)),
+    setExitCode: Boolean = false
+): Boolean {
+
+    if (System.getenv("METALAVA_DUMP_ARGV") != null &&
+        !java.lang.Boolean.getBoolean("METALAVA_TESTS_RUNNING")
+    ) {
+        stdout.println("---Running $PROGRAM_NAME----")
+        stdout.println("pwd=${File("").absolutePath}")
+        args.forEach { arg ->
+            stdout.println("\"$arg\",")
+        }
+        stdout.println("----------------------------")
+    }
+
+    try {
+        val modifiedArgs =
+            if (args.isEmpty()) {
+                arrayOf("--help")
+            } else {
+                args
+            }
+
+        options = Options(modifiedArgs, stdout, stderr)
+        compatibility = Compatibility(options.compatOutput)
+        processFlags()
+        stdout.flush()
+        stderr.flush()
+
+        if (setExitCode && reporter.hasErrors()) {
+            System.exit(-1)
+        }
+        return true
+    } catch (e: Options.OptionsException) {
+        if (e.stderr.isNotBlank()) {
+            stderr.println("\n${e.stderr}")
+        }
+        if (e.stdout.isNotBlank()) {
+            stdout.println("\n${e.stdout}")
+        }
+        if (setExitCode) {
+            stdout.flush()
+            stderr.flush()
+            System.exit(e.exitCode)
+        }
+    }
+    stdout.flush()
+    stderr.flush()
+    return false
+}
+
+private fun processFlags() {
+    val stopwatch = Stopwatch.createStarted()
+
+    val androidApiLevelXml = options.generateApiLevelXml
+    val apiLevelJars = options.apiLevelJars
+    if (androidApiLevelXml != null && apiLevelJars != null) {
+        ApiGenerator.generate(apiLevelJars, androidApiLevelXml)
+
+        if (options.apiJar == null && options.sources.isEmpty() &&
+            options.sourcePath.isEmpty() && options.previousApi == null
+        ) {
+            // Done
+            return
+        }
+    }
+
+    val codebase =
+        if (options.sources.size == 1 && options.sources[0].path.endsWith(SdkConstants.DOT_TXT)) {
+            loadFromSignatureFiles(
+                file = options.sources[0], kotlinStyleNulls = options.inputKotlinStyleNulls,
+                manifest = options.manifest, performChecks = true, supportsStagedNullability = true
+            )
+        } else if (options.apiJar != null) {
+            loadFromJarFile(options.apiJar!!)
+        } else {
+            loadFromSources()
+        }
+    options.manifest?.let { codebase.manifest = it }
+
+    if (options.verbose) {
+        options.stdout.println("\n$PROGRAM_NAME analyzed API in ${stopwatch.elapsed(TimeUnit.SECONDS)} seconds")
+    }
+
+    val previousApiFile = options.previousApi
+    if (previousApiFile != null) {
+        val previous = loadFromSignatureFiles(
+            previousApiFile, options.inputKotlinStyleNulls,
+            supportsStagedNullability = true
+        )
+        codebase.description = "Source tree"
+        previous.description = "Previous stable API"
+
+        // If configured, compares the new API with the previous API and reports
+        // any incompatibilities.
+        checkCompatibility(codebase, previous)
+
+        // If configured, checks for newly added nullness information compared
+        // to the previous stable API and marks the newly annotated elements
+        // as migrated (which will cause the Kotlin compiler to treat problems
+        // as warnings instead of errors
+
+        migrateNulls(codebase, previous)
+    }
+
+    // Based on the input flags, generates various output files such
+    // as signature files and/or stubs files
+    generateOutputs(codebase)
+
+    // Coverage stats?
+    if (options.dumpAnnotationStatistics) {
+        progress("\nMeasuring annotation statistics: ")
+        AnnotationStatistics(codebase).count()
+    }
+    if (options.annotationCoverageOf.isNotEmpty()) {
+        progress("\nMeasuring annotation coverage: ")
+        AnnotationStatistics(codebase).measureCoverageOf(options.annotationCoverageOf)
+    }
+
+    Disposer.dispose(LintCoreApplicationEnvironment.get().parentDisposable)
+
+    if (!options.quiet) {
+        val packageCount = codebase.size()
+        options.stdout.println("\n$PROGRAM_NAME finished handling $packageCount packages in $stopwatch")
+        options.stdout.flush()
+    }
+}
+
+private fun migrateNulls(codebase: Codebase, previous: Codebase) {
+    if (options.migrateNulls) {
+        val prev = previous.supportsStagedNullability
+        try {
+            previous.supportsStagedNullability = true
+            previous.compareWith(
+                NullnessMigration(), codebase,
+                ApiPredicate(codebase)
+            )
+        } finally {
+            previous.supportsStagedNullability = prev
+        }
+    }
+}
+
+private fun checkCompatibility(codebase: Codebase, previous: Codebase) {
+    if (options.checkCompatibility) {
+        previous.compareWith(CompatibilityCheck(), codebase, ApiPredicate(codebase))
+    }
+}
+
+private fun loadFromSignatureFiles(
+    file: File, kotlinStyleNulls: Boolean,
+    manifest: File? = null,
+    performChecks: Boolean = false,
+    supportsStagedNullability: Boolean = false
+): Codebase {
+    try {
+        val codebase = ApiFile.parseApi(File(file.path), kotlinStyleNulls, supportsStagedNullability)
+        codebase.manifest = manifest
+        codebase.description = "Codebase loaded from ${file.name}"
+
+        if (performChecks) {
+            val analyzer = ApiAnalyzer(codebase)
+            analyzer.performChecks()
+        }
+        return codebase
+    } catch (ex: ApiParseException) {
+        val message = "Unable to parse signature file $file: ${ex.message}"
+        throw Options.OptionsException(message)
+    }
+}
+
+private fun loadFromSources(): Codebase {
+    val projectEnvironment = createProjectEnvironment()
+
+    progress("\nProcessing sources: ")
+
+    val sources = if (options.sources.isEmpty()) {
+        if (!options.quiet) {
+            options.stdout.println("No source files specified: recursively including all sources found in the source path")
+        }
+        gatherSources(options.sourcePath)
+    } else {
+        options.sources
+    }
+
+    val joined = mutableListOf<File>()
+    joined.addAll(options.sourcePath.map { it.absoluteFile })
+    joined.addAll(options.classpath.map { it.absoluteFile })
+    // Add in source roots implied by the source files
+    extractRoots(sources, joined)
+
+    // Create project environment with those paths
+    projectEnvironment.registerPaths(joined)
+    val project = projectEnvironment.project
+
+    val kotlinFiles = sources.filter { it.path.endsWith(SdkConstants.DOT_KT) }
+    KotlinLintAnalyzerFacade.analyze(kotlinFiles, joined, project)
+
+    val units = Extractor.createUnitsForFiles(project, sources)
+    val packageDocs = gatherHiddenPackagesFromJavaDocs(options.sourcePath)
+
+    progress("\nReading Codebase: ")
+
+    val codebase = PsiBasedCodebase("Codebase loaded from source folders")
+    codebase.initialize(project, units, packageDocs)
+    codebase.manifest = options.manifest
+
+    progress("\nAnalyzing API: ")
+
+    val analyzer = ApiAnalyzer(codebase)
+    analyzer.mergeExternalAnnotations()
+    analyzer.computeApi()
+    analyzer.handleStripping()
+
+    progress("\nInsert missing constructors: ")
+    val ignoreShown = options.showUnannotated
+
+    // Compute default constructors (and add missing package private constructors
+    // to make stubs compilable if necessary)
+    if (options.stubsDir != null) {
+        val filterEmit = ApiPredicate(codebase, ignoreShown = ignoreShown, ignoreRemoved = false)
+        analyzer.addConstructors(filterEmit)
+
+        val apiEmit = ApiPredicate(codebase, ignoreShown = ignoreShown)
+        val apiReference = ApiPredicate(codebase, ignoreShown = true)
+
+        // Copy methods from soon-to-be-hidden parents into descendant classes, when necessary
+        progress("\nInsert missing stubs methods: ")
+        analyzer.generateInheritedStubs(apiEmit, apiReference)
+
+        progress("\nEnhancing docs: ")
+        val docAnalyzer = DocAnalyzer(codebase)
+        docAnalyzer.enhance()
+
+        val applyApiLevelsXml = options.applyApiLevelsXml
+        if (applyApiLevelsXml != null) {
+            progress("\nApplying API levels")
+            docAnalyzer.applyApiLevels(applyApiLevelsXml)
+        }
+    }
+
+    progress("\nPerforming misc API checks: ")
+    analyzer.performChecks()
+
+//    // TODO: Move the filtering earlier
+//    progress("\nFiltering API: ")
+//    return filterCodebase(codebase)
+    return codebase
+}
+
+private fun filterCodebase(codebase: PsiBasedCodebase): Codebase {
+    val ignoreShown = options.showAnnotations.isEmpty()
+
+    // We ignore removals when limiting the API
+    val apiFilter = FilterPredicate(ApiPredicate(codebase, ignoreShown = ignoreShown))
+    val apiReference = ApiPredicate(codebase, ignoreShown = true)
+    val apiEmit = apiFilter.and(ElidingPredicate(apiReference))
+
+    return codebase.filter(apiEmit, apiReference)
+}
+
+private fun loadFromJarFile(apiJar: File, manifest: File? = null): Codebase {
+    val projectEnvironment = createProjectEnvironment()
+
+    progress("Processing jar file: ")
+
+    // Create project environment with those paths
+    val project = projectEnvironment.project
+    projectEnvironment.registerPaths(listOf(apiJar))
+    val codebase = PsiBasedCodebase()
+    codebase.initialize(project, apiJar)
+    if (manifest != null) {
+        codebase.manifest = options.manifest
+    }
+    val analyzer = ApiAnalyzer(codebase)
+    analyzer.mergeExternalAnnotations()
+    codebase.description = "Codebase loaded from ${apiJar.name}"
+    return codebase
+}
+
+private fun createProjectEnvironment(): LintCoreProjectEnvironment {
+    ensurePsiFileCapacity()
+    val appEnv = LintCoreApplicationEnvironment.get()
+    val parentDisposable = Disposer.newDisposable()
+    return LintCoreProjectEnvironment.create(parentDisposable, appEnv)
+}
+
+private fun ensurePsiFileCapacity() {
+    val fileSize = System.getProperty("idea.max.intellisense.filesize")
+    if (fileSize == null) {
+        // Ensure we can handle large compilation units like android.R
+        System.setProperty("idea.max.intellisense.filesize", "100000")
+    }
+}
+
+private fun generateOutputs(codebase: Codebase) {
+
+    options.apiFile?.let { apiFile ->
+        val apiFilter = FilterPredicate(ApiPredicate(codebase))
+        val apiReference = ApiPredicate(codebase, ignoreShown = true)
+        val apiEmit = apiFilter.and(ElidingPredicate(apiReference))
+
+        createReportFile(codebase, apiFile, "API", { printWriter ->
+            val preFiltered = codebase.original != null
+            SignatureWriter(printWriter, apiEmit, apiReference, preFiltered)
+        })
+    }
+
+    options.removedApiFile?.let { apiFile ->
+        val unfiltered = codebase.original ?: codebase
+
+        val removedFilter = FilterPredicate(ApiPredicate(codebase, matchRemoved = true))
+        val removedReference = ApiPredicate(codebase, ignoreShown = true, ignoreRemoved = true)
+        val removedEmit = removedFilter.and(ElidingPredicate(removedReference))
+
+        createReportFile(unfiltered, apiFile, "removed API", { printWriter ->
+            SignatureWriter(printWriter, removedEmit, removedReference, codebase.original != null)
+        })
+    }
+
+    options.privateApiFile?.let { apiFile ->
+        val apiFilter = FilterPredicate(ApiPredicate(codebase))
+        val privateEmit = apiFilter.negate()
+        val privateReference = Predicate<Item> { true }
+
+        createReportFile(codebase, apiFile, "private API", { printWriter ->
+            SignatureWriter(printWriter, privateEmit, privateReference, codebase.original != null)
+        })
+    }
+
+    options.privateDexApiFile?.let { apiFile ->
+        val apiFilter = FilterPredicate(ApiPredicate(codebase))
+        val privateEmit = apiFilter.negate()
+        val privateReference = Predicate<Item> { true }
+
+        createReportFile(codebase, apiFile, "DEX API",
+            { printWriter -> DexApiWriter(printWriter, privateEmit, privateReference) })
+    }
+
+    options.proguard?.let { proguard ->
+        val apiEmit = FilterPredicate(ApiPredicate(codebase))
+        val apiReference = ApiPredicate(codebase, ignoreShown = true)
+        createReportFile(codebase, proguard, "Proguard file",
+            { printWriter -> ProguardWriter(printWriter, apiEmit, apiReference) })
+    }
+
+    options.stubsDir?.let { createStubFiles(it, codebase) }
+    // Otherwise, if we've asked to write out a file list, write out the
+    // input file list instead
+            ?: options.stubsSourceList?.let { file ->
+                val root = File("").absoluteFile
+                val sources = options.sources
+                val rootPath = root.path
+                val contents = sources.joinToString(" ") {
+                    val path = it.path
+                    if (path.startsWith(rootPath)) {
+                        path.substring(rootPath.length)
+                    } else {
+                        path
+                    }
+                }
+                Files.asCharSink(file, UTF_8).write(contents)
+            }
+
+    options.externalAnnotations?.let { extractAnnotations(codebase, it) }
+    progress("\n")
+}
+
+private fun extractAnnotations(codebase: Codebase, file: File) {
+    val localTimer = Stopwatch.createStarted()
+    val units = codebase.units
+
+    @Suppress("UNCHECKED_CAST")
+    ExtractAnnotations().extractAnnotations(units.asSequence().filter { it is PsiClassOwner }.toList() as List<PsiClassOwner>)
+    if (options.verbose) {
+        options.stdout.print("\n$PROGRAM_NAME extracted annotations into $file in $localTimer")
+        options.stdout.flush()
+    }
+}
+
+private fun createStubFiles(stubDir: File, codebase: Codebase) {
+    // Generating stubs from a sig-file-based codebase is problematic
+    assert(codebase.supportsDocumentation())
+
+    progress("\nGenerating stub files: ")
+    val localTimer = Stopwatch.createStarted()
+    val prevCompatibility = compatibility
+    if (compatibility.compat) {
+        //if (!options.quiet) {
+        //    options.stderr.println("Warning: Turning off compat mode when generating stubs")
+        //}
+        compatibility = Compatibility(false)
+        // But preserve the setting for whether we want to erase throws signatures (to ensure the API
+        // stays compatible)
+        compatibility.useErasureInThrows = prevCompatibility.useErasureInThrows
+    }
+
+    val stubWriter =
+        StubWriter(
+            codebase = codebase, stubsDir = stubDir, generateAnnotations = options.generateAnnotations,
+            preFiltered = codebase.original != null
+        )
+    codebase.accept(stubWriter)
+
+    // Optionally also write out a list of source files that were generated; used
+    // for example to point javadoc to the stubs output to generate documentation
+    options.stubsSourceList?.let {
+        val root = File("").absoluteFile
+        stubWriter.writeSourceList(it, root)
+    }
+
+    /*
+    // Temporary hack: Also write out annotations to make stub compilation work. This is
+    // just temporary: the Makefiles for the platform should be updated to supply a
+    // bootclasspath instead.
+    val nullable = File(stubDir, "android/support/annotation/Nullable.java")
+    val nonnull = File(stubDir, "android/support/annotation/NonNull.java")
+    nullable.parentFile.mkdirs()
+    nonnull.parentFile.mkdirs()
+    Files.asCharSink(nullable, UTF_8).write(
+        "package android.support.annotation;\n" +
+                "import java.lang.annotation.*;\n" +
+                "import static java.lang.annotation.ElementType.*;\n" +
+                "import static java.lang.annotation.RetentionPolicy.SOURCE;\n" +
+                "@SuppressWarnings(\"WeakerAccess\")\n" +
+                "@Retention(SOURCE)\n" +
+                "@Target({METHOD, PARAMETER, FIELD})\n" +
+                "public @interface Nullable {\n" +
+                "}\n"
+    )
+    Files.asCharSink(nonnull, UTF_8).write(
+        "package android.support.annotation;\n" +
+                "import java.lang.annotation.*;\n" +
+                "import static java.lang.annotation.ElementType.*;\n" +
+                "import static java.lang.annotation.RetentionPolicy.SOURCE;\n" +
+                "@SuppressWarnings(\"WeakerAccess\")\n" +
+                "@Retention(SOURCE)\n" +
+                "@Target({METHOD, PARAMETER, FIELD})\n" +
+                "public @interface NonNull {\n" +
+                "}\n"
+    )
+    */
+
+    compatibility = prevCompatibility
+
+    progress("\n$PROGRAM_NAME wrote stubs directory $stubDir in ${localTimer.elapsed(SECONDS)} seconds")
+}
+
+private fun progress(message: String) {
+    if (options.verbose) {
+        options.stdout.print(message)
+        options.stdout.flush()
+    }
+}
+
+private fun createReportFile(
+    codebase: Codebase,
+    apiFile: File,
+    description: String,
+    createVisitor: (PrintWriter) -> ApiVisitor
+) {
+    progress("\nWriting $description file: ")
+    val localTimer = Stopwatch.createStarted()
+    try {
+        val writer = PrintWriter(Files.asCharSink(apiFile, Charsets.UTF_8).openBufferedStream())
+        writer.use { printWriter ->
+            val apiWriter = createVisitor(printWriter)
+            codebase.accept(apiWriter)
+        }
+    } catch (e: IOException) {
+        reporter.report(Errors.IO_ERROR, apiFile, "Cannot open file for write.")
+    }
+    if (options.verbose) {
+        options.stdout.print("\n$PROGRAM_NAME wrote $description file $apiFile in ${localTimer.elapsed(SECONDS)} seconds")
+    }
+}
+
+/** Used for verbose output to show progress bar */
+private var tick = 0
+
+/** Print progress */
+fun tick() {
+    tick++
+    if (tick % 100 == 0) {
+        options.stdout.print(".")
+        options.stdout.flush()
+    }
+}
+
+private fun addSourceFiles(list: MutableList<File>, file: File) {
+    if (file.isDirectory) {
+        val files = file.listFiles()
+        if (files != null) {
+            for (child in files) {
+                addSourceFiles(list, child)
+            }
+        }
+    } else {
+        if (file.isFile && (file.path.endsWith(DOT_JAVA) || file.path.endsWith(DOT_KT))) {
+            list.add(file)
+        }
+    }
+}
+
+fun gatherSources(sourcePath: List<File>): List<File> {
+    val sources = Lists.newArrayList<File>()
+    for (file in sourcePath) {
+        addSourceFiles(sources, file.absoluteFile)
+    }
+    return sources
+}
+
+private fun addHiddenPackages(
+    packageToDoc: MutableMap<String, String>,
+    hiddenPackages: MutableSet<String>,
+    file: File,
+    pkg: String
+) {
+    if (file.isDirectory) {
+        val files = file.listFiles()
+        if (files != null) {
+            for (child in files) {
+                val subPkg =
+                    if (child.isDirectory)
+                        if (pkg.isEmpty())
+                            child.name
+                        else
+                            pkg + "." + child.name
+                    else
+                        pkg
+                addHiddenPackages(packageToDoc, hiddenPackages, child, subPkg)
+            }
+        }
+    } else if (file.isFile && file.name == "package.html") {
+        val contents = Files.asCharSource(file, Charsets.UTF_8).read()
+        packageToDoc.put(pkg, contents)
+        if (contents.contains("@hide")) {
+            hiddenPackages.add(pkg)
+        }
+    }
+}
+
+private fun gatherHiddenPackagesFromJavaDocs(sourcePath: List<File>): PackageDocs {
+    val map = HashMap<String, String>(100)
+    val set = HashSet<String>(100)
+    for (file in sourcePath) {
+        addHiddenPackages(map, set, file, "")
+    }
+    return PackageDocs(map, set)
+}
+
+private fun extractRoots(sources: List<File>, sourceRoots: MutableList<File> = mutableListOf()): List<File> {
+    // Cache for each directory since computing root for a source file is
+    // expensive
+    val dirToRootCache = mutableMapOf<String, File>()
+    for (file in sources) {
+        val parent = file.parentFile ?: continue
+        val found = dirToRootCache[parent.path]
+        if (found != null) {
+            continue
+        }
+
+        val root = findRoot(file) ?: continue
+        dirToRootCache.put(parent.path, root)
+
+        if (!sourceRoots.contains(root)) {
+            sourceRoots.add(root)
+        }
+    }
+
+    return sourceRoots
+}
+
+/**
+ * If given a full path to a Java or Kotlin source file, produces the path to
+ * the source root if possible.
+ */
+private fun findRoot(file: File): File? {
+    val path = file.path
+    if (path.endsWith(DOT_JAVA) || path.endsWith(DOT_KT)) {
+        val pkg = findPackage(file) ?: return null
+        val parent = file.parentFile ?: return null
+        return File(path.substring(0, parent.path.length - pkg.length))
+    }
+
+    return null
+}
+
+/** Finds the package of the given Java/Kotlin source file, if possible */
+fun findPackage(file: File): String? {
+    val source = Files.asCharSource(file, Charsets.UTF_8).read()
+    return findPackage(source)
+}
+
+@Suppress("PrivatePropertyName")
+private val PACKAGE_PATTERN = Pattern.compile("package\\s+([\\S&&[^;]]*)")
+
+/** Finds the package of the given Java/Kotlin source code, if possible */
+fun findPackage(source: String): String? {
+    val matcher = PACKAGE_PATTERN.matcher(source)
+    val foundPackage = matcher.find()
+    return if (foundPackage) {
+        matcher.group(1).trim { it <= ' ' }
+    } else {
+        null
+    }
+}
+
+data class PackageDocs(val packageDocs: MutableMap<String, String>, val hiddenPackages: MutableSet<String>)
diff --git a/src/main/java/com/android/tools/metalava/ExtractAnnotations.kt b/src/main/java/com/android/tools/metalava/ExtractAnnotations.kt
new file mode 100644
index 0000000..f97c112
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/ExtractAnnotations.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava
+
+import com.android.tools.lint.annotations.Extractor
+import com.intellij.psi.PsiClassOwner
+
+class ExtractAnnotations {
+    fun extractAnnotations(units: List<PsiClassOwner>) {
+        val rmTypeDefs = if (options.rmTypeDefs != null) listOf(options.rmTypeDefs) else emptyList()
+        val typedefFile = options.typedefFile
+        val filter = options.apiFilter
+
+        val verbose = !options.quiet
+        val skipClassRetention = options.skipClassRetention
+        val extractor = Extractor(filter, rmTypeDefs, verbose, !skipClassRetention, true)
+        extractor.isListIgnored = !options.hideFiltered
+        extractor.extractFromProjectSource(units)
+        for (jar in options.mergeAnnotations) {
+            extractor.mergeExisting(jar)
+        }
+
+        extractor.export(options.externalAnnotations, null)
+
+        if (typedefFile != null) {
+            extractor.writeTypedefFile(typedefFile)
+        }
+
+        if (rmTypeDefs.isNotEmpty()) {
+            if (typedefFile != null) {
+                Extractor.removeTypedefClasses(rmTypeDefs, typedefFile)
+            } else {
+                extractor.removeTypedefClasses()
+            }
+        }
+    }
+}
diff --git a/src/main/java/com/android/tools/metalava/NullnessMigration.kt b/src/main/java/com/android/tools/metalava/NullnessMigration.kt
new file mode 100644
index 0000000..60c727f
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/NullnessMigration.kt
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava
+
+import com.android.tools.metalava.model.AnnotationItem
+import com.android.tools.metalava.model.Item
+
+/**
+ * Performs null migration analysis, looking at previous API signature
+ * files and new signature files, and replacing new @Nullable and @NonNull
+ * annotations with @NewlyNullable and @NewlyNonNull, and similarly
+ * moving @NewlyNullable and @NewlyNonNull to @RecentlyNullable and @RecentlyNonNull
+ * (and finally once the annotations have been there for another API level,
+ * finally moving them to unconditionally nullable/nonnull.)
+ *
+ * (Newly null is the initial level; user code is marked as warnings if in
+ * conflict with the annotation. Recently null is the next level; once an
+ * API has had newly-null metadata in one API level, it gets promoted to
+ * recently, which generates errors instead of warnings. The reason we have
+ * this instead of just making it unconditional is that you can still invoke
+ * the compiler with a flag to defeat it, so the Kotlin team suggested we do
+ * this.
+ *
+ * TODO: Enforce compatibility across type use annotations, e.g.
+ * changing parameter value from
+ *    {@code @NonNull List<@Nullable String>}
+ * to
+ *    {@code @NonNull List<@NonNull String>}
+ * is forbidden.
+ */
+class NullnessMigration : ComparisonVisitor() {
+    override fun compare(old: Item, new: Item) {
+        if (hasNullnessInformation(new)) {
+            if (!hasNullnessInformation(old)) {
+                // Nullness information change: Add migration annotation
+                val annotation = if (isNullable(new)) NEWLY_NULLABLE else NEWLY_NONNULL
+
+                val migration = findNullnessAnnotation(new) ?: return
+                val modifiers = new.mutableModifiers()
+                modifiers.removeAnnotation(migration)
+
+                // Don't map annotation names - this would turn newly non null back into non null
+                modifiers.addAnnotation(new.codebase.createAnnotation("@" + annotation, new, mapName = false))
+            } else if (hasMigrationAnnotation(old)) {
+                // Already marked migration before: Now we can promote it to
+                // no longer migrated!
+                val nullAnnotation = findNullnessAnnotation(new) ?: return
+                val migration = findMigrationAnnotation(old)?.toSource() ?: return
+                val modifiers = new.mutableModifiers()
+                modifiers.removeAnnotation(nullAnnotation)
+
+                if (isNewlyMigrated(old)) {
+                    // Move from newly to recently
+                    val source = migration.replace("Newly", "Recently")
+                    modifiers.addAnnotation(new.codebase.createAnnotation(source, new, mapName = false))
+                } else {
+                    // Move from recently to no longer marked as migrated
+                    val source = migration.replace("Newly", "").replace("Recently", "")
+                    modifiers.addAnnotation(new.codebase.createAnnotation(source, new, mapName = false))
+                }
+            }
+        }
+    }
+
+    companion object {
+        fun hasNullnessInformation(item: Item): Boolean {
+            return isNullable(item) || isNonNull(item)
+        }
+
+        fun findNullnessAnnotation(item: Item): AnnotationItem? {
+            return item.modifiers.annotations().firstOrNull { it.isNullnessAnnotation() }
+        }
+
+        fun findMigrationAnnotation(item: Item): AnnotationItem? {
+            return item.modifiers.annotations().firstOrNull {
+                val qualifiedName = it.qualifiedName() ?: ""
+                isMigrationAnnotation(qualifiedName)
+            }
+        }
+
+        fun isNullable(item: Item): Boolean {
+            return item.modifiers.annotations().any { it.isNullable() }
+        }
+
+        fun isNonNull(item: Item): Boolean {
+            return item.modifiers.annotations().any { it.isNonNull() }
+        }
+
+        fun hasMigrationAnnotation(item: Item): Boolean {
+            return item.modifiers.annotations().any { isMigrationAnnotation(it.qualifiedName() ?: "") }
+        }
+
+        fun isNewlyMigrated(item: Item): Boolean {
+            return item.modifiers.annotations().any { isNewlyMigrated(it.qualifiedName() ?: "") }
+        }
+
+        fun isRecentlyMigrated(item: Item): Boolean {
+            return item.modifiers.annotations().any { isRecentlyMigrated(it.qualifiedName() ?: "") }
+        }
+
+        fun isNewlyMigrated(qualifiedName: String): Boolean {
+            return qualifiedName.endsWith(".NewlyNullable") ||
+                    qualifiedName.endsWith(".NewlyNonNull")
+        }
+
+        fun isRecentlyMigrated(qualifiedName: String): Boolean {
+            return qualifiedName.endsWith(".RecentlyNullable") ||
+                    qualifiedName.endsWith(".RecentlyNonNull")
+        }
+
+        fun isMigrationAnnotation(qualifiedName: String): Boolean {
+            return isNewlyMigrated(qualifiedName) || isRecentlyMigrated(qualifiedName)
+        }
+    }
+}
+
+/**
+ * @TypeQualifierNickname
+ * @NonNull
+ * @kotlin.annotations.jvm.UnderMigration(status = kotlin.annotations.jvm.MigrationStatus.WARN)
+ * @Retention(RetentionPolicy.CLASS)
+ * public @interface NewlyNullable {
+ * }
+ */
+const val NEWLY_NULLABLE = "android.support.annotation.NewlyNullable"
+
+/**
+ * @TypeQualifierNickname
+ * @NonNull
+ * @kotlin.annotations.jvm.UnderMigration(status = kotlin.annotations.jvm.MigrationStatus.WARN)
+ * @Retention(RetentionPolicy.CLASS)
+ * public @interface NewlyNonNull {
+ * }
+ */
+const val NEWLY_NONNULL = "android.support.annotation.NewlyNonNull"
+
+/**
+ * @TypeQualifierNickname
+ * @NonNull
+ * @kotlin.annotations.jvm.UnderMigration(status = kotlin.annotations.jvm.MigrationStatus.STRICT)
+ * @Retention(RetentionPolicy.CLASS)
+ * public @interface NewlyNullable {
+ * }
+ */
+
+const val RECENTLY_NULLABLE = "android.support.annotation.RecentlyNullable"
+/**
+ * @TypeQualifierNickname
+ * @NonNull
+ * @kotlin.annotations.jvm.UnderMigration(status = kotlin.annotations.jvm.MigrationStatus.STRICT)
+ * @Retention(RetentionPolicy.CLASS)
+ * public @interface NewlyNonNull {
+ * }
+ */
+const val RECENTLY_NONNULL = "android.support.annotation.RecentlyNonNull"
+
diff --git a/src/main/java/com/android/tools/metalava/Options.kt b/src/main/java/com/android/tools/metalava/Options.kt
new file mode 100644
index 0000000..6fe1652
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/Options.kt
@@ -0,0 +1,1116 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava
+
+import com.android.SdkConstants
+import com.android.tools.lint.annotations.ApiDatabase
+import com.android.tools.lint.annotations.SdkUtils2
+import com.android.tools.lint.annotations.SdkUtils2.wrap
+import com.android.tools.metalava.doclava1.Errors
+import com.google.common.base.CharMatcher
+import com.google.common.base.Splitter
+import com.google.common.collect.Lists
+import com.google.common.io.Files
+import java.io.File
+import java.io.IOException
+import java.io.OutputStreamWriter
+import java.io.PrintWriter
+import java.io.StringWriter
+
+/** Global options for the metadata extraction tool */
+var options = Options(emptyArray())
+
+private const val MAX_LINE_WIDTH = 90
+
+private const val ARG_HELP = "--help"
+private const val ARG_QUIET = "--quiet"
+private const val ARG_VERBOSE = "--verbose"
+private const val ARG_CLASS_PATH = "--classpath"
+private const val ARG_SOURCE_PATH = "--source-path"
+private const val ARG_SOURCE_FILES = "--source-files"
+private const val ARG_API = "--api"
+private const val ARG_PRIVATE_API = "--private-api"
+private const val ARG_PRIVATE_DEX_API = "--private-dex-api"
+private const val ARGS_COMPAT_OUTPUT = "--compatible-output"
+private const val ARG_REMOVED_API = "--removed-api"
+private const val ARG_MERGE_ANNOTATIONS = "--merge-annotations"
+private const val ARG_INPUT_API_JAR = "--input-api-jar"
+private const val ARG_EXACT_API = "--exact-api"
+private const val ARG_STUBS = "--stubs"
+private const val ARG_STUBS_SOURCE_LIST = "--write-stubs-source-list"
+private const val ARG_PROGUARD = "--proguard"
+private const val ARG_EXTRACT_ANNOTATIONS = "--extract-annotations"
+private const val ARG_EXCLUDE_ANNOTATIONS = "--exclude-annotations"
+private const val ARG_API_FILTER = "--api-filter"
+private const val ARG_RM_TYPEDEFS = "--rmtypedefs"
+private const val ARG_TYPEDEF_FILE = "--typedef-file"
+private const val ARG_SKIP_CLASS_RETENTION = "--skip-class-retention"
+private const val ARG_HIDE_FILTERED = "--hide-filtered"
+private const val ARG_HIDE_PACKAGE = "--hide-package"
+private const val ARG_MANIFEST = "--manifest"
+private const val ARG_PREVIOUS_API = "--previous-api"
+private const val ARG_MIGRATE_NULLNESS = "--migrate-nullness"
+private const val ARG_CHECK_COMPATIBILITY = "--check-compatibility"
+private const val ARG_INPUT_KOTLIN_NULLS = "--input-kotlin-nulls"
+private const val ARG_OUTPUT_KOTLIN_NULLS = "--output-kotlin-nulls"
+private const val ARG_ANNOTATION_COVERAGE_STATS = "--annotation-coverage-stats"
+private const val ARG_ANNOTATION_COVERAGE_OF = "--annotation-coverage-of"
+private const val ARG_WARNINGS_AS_ERRORS = "--warnings-as-errors"
+private const val ARG_LINTS_AS_ERRORS = "--lints-as-errors"
+private const val ARG_SHOW_ANNOTATION = "--show-annotation"
+private const val ARG_SHOW_UNANNOTATED = "--show-unannotated"
+private const val ARG_COLOR = "--color"
+private const val ARG_NO_COLOR = "--no-color"
+private const val ARG_OMIT_COMMON_PACKAGES = "--omit-common-packages"
+private const val ARG_SKIP_JAVA_IN_COVERAGE_REPORT = "--skip-java-in-coverage-report"
+private const val ARG_NO_BANNER = "--no-banner"
+private const val ARG_ERROR = "--error"
+private const val ARG_WARNING = "--warning"
+private const val ARG_LINT = "--lint"
+private const val ARG_HIDE = "--hide"
+private const val ARG_UNHIDE_CLASSPATH_CLASSES = "--unhide-classpath-classes"
+private const val ARG_ALLOW_REFERENCING_UNKNOWN_CLASSES = "--allow-referencing-unknown-classes"
+private const val ARG_NO_UNKNOWN_CLASSES = "--no-unknown-classes"
+private const val ARG_INCLUDE_DOC_ONLY = "--include-doconly"
+private const val ARG_APPLY_API_LEVELS = "--apply-api-levels"
+private const val ARG_GENERATE_API_LEVELS = "--generate-api-levels"
+private const val ARG_ANDROID_JAR_PATTERN = "--android-jar-pattern"
+private const val ARG_CURRENT_VERSION = "--current-version"
+private const val ARG_CURRENT_CODENAME = "--current-codename"
+private const val ARG_CURRENT_JAR = "--current-jar"
+
+class Options(
+    args: Array<String>,
+    /** Writer to direct output to */
+    var stdout: PrintWriter = PrintWriter(OutputStreamWriter(System.out)),
+    /** Writer to direct error messages to */
+    var stderr: PrintWriter = PrintWriter(OutputStreamWriter(System.err))
+) {
+
+    /** Internal list backing [sources] */
+    private val mutableSources: MutableList<File> = mutableListOf()
+    /** Internal list backing [sourcePath] */
+    private val mutableSourcePath: MutableList<File> = mutableListOf()
+    /** Internal list backing [classpath] */
+    private val mutableClassPath: MutableList<File> = mutableListOf()
+    /** Internal list backing [showAnnotations] */
+    private val mutableShowAnnotations: MutableList<String> = mutableListOf()
+    /** Internal list backing [hideAnnotations] */
+    private val mutableHideAnnotations: MutableList<String> = mutableListOf()
+    /** Internal list backing [stubImportPackages] */
+    private val mutableStubImportPackages: MutableSet<String> = mutableSetOf()
+    /** Internal list backing [mergeAnnotations] */
+    private val mutableMergeAnnotations: MutableList<File> = mutableListOf()
+    /** Internal list backing [annotationCoverageOf] */
+    private val mutableAnnotationCoverageOf: MutableList<File> = mutableListOf()
+    /** Internal list backing [hidePackages] */
+    private val mutableHidePackages: MutableList<String> = mutableListOf()
+    /** Internal list backing [skipEmitPackages] */
+    private val mutableSkipEmitPackages: MutableList<String> = mutableListOf()
+
+    /** Ignored flags we've already warned about - store here such that we don't keep reporting them */
+    private val alreadyWarned: MutableSet<String> = mutableSetOf()
+
+    /**
+     * Whether signature files should emit in "compat" mode, preserving the various
+     * quirks of the previous signature file format -- this will for example use a non-standard
+     * modifier ordering, it will call enums interfaces, etc. See the [Compatibility] class
+     * for more fine grained control (which is not (currently) exposed as individual command line
+     * flags.
+     */
+    var compatOutput = COMPAT_MODE_BY_DEFAULT && !args.contains("$ARGS_COMPAT_OUTPUT=no")
+
+    /** Whether nullness annotations should be displayed as ?/!/empty instead of with @NonNull/@Nullable. */
+    var outputKotlinStyleNulls = !compatOutput
+
+    /** Whether we should omit common packages such as java.lang.* and kotlin.* from signature output */
+    var omitCommonPackages = !compatOutput
+
+    /**
+     * Whether reading signature files should assume the input is formatted as Kotlin-style nulls
+     * (e.g. ? means nullable, ! means unknown, empty means not null)
+     */
+    var inputKotlinStyleNulls = false
+
+    /** If true, treat all warnings as errors */
+    var warningsAreErrors: Boolean = false
+
+    /** If true, treat all API lint warnings as errors */
+    var lintsAreErrors: Boolean = false
+
+    /** The list of source roots */
+    val sourcePath: List<File> = mutableSourcePath
+
+    /** The list of dependency jars */
+    val classpath: List<File> = mutableClassPath
+
+    /** All source files to parse */
+    var sources: List<File> = mutableSources
+
+    /** Whether to include APIs with annotations (intended for documentation purposes) */
+    var showAnnotations = mutableShowAnnotations
+
+    /** Whether to include unannotated elements if {@link #showAnnotations} is set */
+    var showUnannotated = false
+
+    /** Packages to include (if null, include all) */
+    var stubPackages: PackageFilter? = null
+
+    /** Packages to import (if empty, include all) */
+    var stubImportPackages: Set<String> = mutableStubImportPackages
+
+    /** Packages to exclude/hide */
+    var hidePackages = mutableHidePackages
+
+    /** Packages that we should skip generating even if not hidden; typically only used by tests */
+    var skipEmitPackages = mutableSkipEmitPackages
+
+    var showAnnotationOverridesVisibility: Boolean = false
+
+    /** Annotations to hide */
+    var hideAnnotations = mutableHideAnnotations
+
+    /** Whether to report warnings and other diagnostics along the way */
+    var quiet = false
+
+    /** Whether to report extra diagnostics along the way (note that verbose isn't the same as not quiet) */
+    var verbose = false
+
+    /** If set, a directory to write stub files to. Corresponds to the --stubs/-stubs flag. */
+    var stubsDir: File? = null
+
+    /** If set, a source file to write the stub index (list of source files) to. Can be passed to
+     * other tools like javac/javadoc using the special @-syntax. */
+    var stubsSourceList: File? = null
+
+    /** Proguard Keep list file to write */
+    var proguard: File? = null
+
+    /** If set, a file to write an API file to. Corresponds to the --api/-api flag. */
+    var apiFile: File? = null
+
+    /** If set, a file to write the private API file to. Corresponds to the --private-api/-privateApi flag. */
+    var privateApiFile: File? = null
+
+    /** If set, a file to write the private DEX signatures to. Corresponds to --private-dex-api. */
+    var privateDexApiFile: File? = null
+
+    /** If set, a file to write extracted annotations to. Corresponds to the --extract-annotations flag. */
+    var externalAnnotations: File? = null
+
+    /** A manifest file to read to for example look up available permissions */
+    var manifest: File? = null
+
+    /** If set, a file to write an API file to. Corresponds to the --removed-api/-removedApi flag. */
+    var removedApiFile: File? = null
+
+    /** Whether output should be colorized */
+    var color = System.getenv("TERM")?.startsWith("xterm") ?: false
+
+    /** Whether to omit Java and Kotlin runtime library packages from annotation coverage stats */
+    var omitRuntimePackageStats = true
+
+    /** Whether to include doc-only-marked items */
+    var includeDocOnly = false
+
+    /** Whether to generate annotations into the stubs */
+    var generateAnnotations = true
+
+    /**
+     * A signature file for the previous version of this API (for compatibility checks, nullness
+     * migration, etc.)
+     */
+    var previousApi: File? = null
+
+    /** Whether we should check API compatibility based on the previous API in [previousApi] */
+    var checkCompatibility: Boolean = false
+
+    /** Whether we should migrate nulls based on the previous API in [previousApi] */
+    var migrateNulls: Boolean = false
+
+    /** Existing external annotation files to merge in */
+    var mergeAnnotations: List<File> = mutableMergeAnnotations
+
+    /** Set of jars and class files for existing apps that we want to measure coverage of */
+    var annotationCoverageOf: List<File> = mutableAnnotationCoverageOf
+
+    /** Framework API definition to restrict included APIs to */
+    var apiFilter: ApiDatabase? = null
+
+    /** If filtering out non-APIs, supply this flag to hide listing matches */
+    var hideFiltered: Boolean = false
+
+    /** Don't extract annotations that have class retention */
+    var skipClassRetention: Boolean = false
+
+    /** Remove typedef classes found in the given folder */
+    var rmTypeDefs: File? = null
+
+    /** Framework API definition to restrict included APIs to */
+    var typedefFile: File? = null
+
+    /** An optional <b>jar</b> file to load classes from instead of from source.
+     * This is similar to the [classpath] attribute except we're explicitly saying
+     * that this is the complete set of classes and that we <b>should</b> generate
+     * signatures/stubs from them or use them to diff APIs with (whereas [classpath]
+     * is only used to resolve types.) */
+    var apiJar: File? = null
+
+    /** Whether to emit coverage statistics for annotations in the API surface */
+    var dumpAnnotationStatistics = false
+
+    /** Only used for tests: Normally we want to treat classes not found as source (e.g. supplied via
+     * classpath) as hidden, but for the unit tests (where we're not passing in
+     * a complete API surface) this makes the tests more cumbersome.
+     * This option lets the testing infrastructure treat these classes differently.
+     * To see the what this means in practice, try turning it back on for the tests
+     * and see what it does to the results :)
+     */
+    var hideClasspathClasses = true
+
+    /** Only used for tests: Whether during code filtering we allow referencing super classes
+     * etc that are unknown (because they're not included in the codebase) */
+    var allowReferencingUnknownClasses = true
+
+    /** Reverse of [allowReferencingUnknownClasses]: Require all classes to be known. This
+     * is used when compiling the main SDK itself (which includes definitions for everything,
+     * including java.lang.Object.) */
+    var noUnknownClasses = false
+
+    /**
+     * mapping from API level to android.jar files, if computing API levels
+     */
+    var apiLevelJars: Array<File>? = null
+
+    /** API level XML file to generate */
+    var generateApiLevelXml: File? = null
+
+    /** Reads API XML file to apply into documentation */
+    var applyApiLevelsXml: File? = null
+
+    init {
+        // Pre-check whether --color/--no-color is present and use that to decide how
+        // to emit the banner even before we emit errors
+        if (args.contains(ARG_NO_COLOR)) {
+            color = false
+        } else if (args.contains(ARG_COLOR) || args.contains("-android")) {
+            color = true
+        }
+        // empty args: only when building initial default Options (options field
+        // at the top of this file; replaced once the driver runs and passes in
+        // a real argv. Don't print a banner when initializing the default options.)
+        if (args.isNotEmpty() && !args.contains(ARG_QUIET) && !args.contains(ARG_NO_BANNER)) {
+            if (color) {
+                stdout.print(colorized(BANNER.trimIndent(), TerminalColor.BLUE))
+            } else {
+                stdout.println(BANNER.trimIndent())
+            }
+        }
+        stdout.println()
+        stdout.flush()
+
+        val apiFilters = mutableListOf<File>()
+        var androidJarPatterns: MutableList<String>? = null
+        var currentApiLevel: Int = -1
+        var currentCodeName: String? = null
+        var currentJar: File? = null
+
+        var index = 0
+        while (index < args.size) {
+            val arg = args[index]
+
+            when (arg) {
+                ARG_HELP, "-h", "-?" -> {
+                    helpAndQuit(color)
+                }
+
+                ARG_QUIET -> {
+                    quiet = true; verbose = false
+                }
+                ARG_VERBOSE -> {
+                    verbose = true; quiet = false
+                }
+
+                ARGS_COMPAT_OUTPUT -> compatOutput = true
+
+            // For now we don't distinguish between bootclasspath and classpath
+                ARG_CLASS_PATH, "-classpath", "-bootclasspath" ->
+                    mutableClassPath.addAll(stringToExistingDirsOrJars(getValue(args, ++index)))
+
+                ARG_SOURCE_PATH, "--sources", "--sourcepath", "-sourcepath" -> {
+                    val path = getValue(args, ++index)
+                    if (path.endsWith(SdkConstants.DOT_JAVA)) {
+                        throw OptionsException(
+                            "$arg should point to a source root directory, not a source file ($path)"
+                        )
+                    }
+                    mutableSourcePath.addAll(stringToExistingDirsOrJars(path))
+                }
+
+                ARG_SOURCE_FILES -> {
+                    val listString = getValue(args, ++index)
+                    listString.split(",").forEach { path ->
+                        mutableSources.addAll(stringToExistingFiles(path))
+                    }
+                }
+
+                ARG_MERGE_ANNOTATIONS, "--merge-zips" -> mutableMergeAnnotations.addAll(
+                    stringToExistingDirsOrFiles(
+                        getValue(args, ++index)
+                    )
+                )
+
+                ARG_API, "-api" -> apiFile = stringToNewFile(getValue(args, ++index))
+
+                ARG_PRIVATE_API, "-privateApi" -> privateApiFile = stringToNewFile(getValue(args, ++index))
+                ARG_PRIVATE_DEX_API, "-privateDexApi" -> privateDexApiFile = stringToNewFile(getValue(args, ++index))
+
+                ARG_REMOVED_API, "-removedApi" -> removedApiFile = stringToNewFile(getValue(args, ++index))
+
+                ARG_EXACT_API, "-exactApi" -> {
+                    unimplemented(arg) // Not yet implemented (because it seems to no longer be hooked up in doclava1)
+                }
+
+                ARG_MANIFEST, "-manifest" -> manifest = stringToExistingFile(getValue(args, ++index))
+
+                ARG_SHOW_ANNOTATION, "-showAnnotation" -> mutableShowAnnotations.add(getValue(args, ++index))
+
+                ARG_SHOW_UNANNOTATED, "-showUnannotated" -> showUnannotated = true
+
+                "--showAnnotationOverridesVisibility" -> {
+                    unimplemented(arg)
+                    showAnnotationOverridesVisibility = true
+                }
+
+                "--hideAnnotations", "-hideAnnotation" -> mutableHideAnnotations.add(getValue(args, ++index))
+
+                ARG_STUBS, "-stubs" -> stubsDir = stringToNewDir(getValue(args, ++index))
+                ARG_STUBS_SOURCE_LIST -> stubsSourceList = stringToNewFile(getValue(args, ++index))
+
+                ARG_EXCLUDE_ANNOTATIONS -> generateAnnotations = false
+
+                ARG_PROGUARD, "-proguard" -> proguard = stringToNewFile(getValue(args, ++index))
+
+                ARG_HIDE_PACKAGE, "-hidePackage" -> mutableHidePackages.add(getValue(args, ++index))
+
+                "--stub-packages", "-stubpackages" -> {
+                    val packages = getValue(args, ++index)
+                    val filter = stubPackages ?: run {
+                        val newFilter = PackageFilter()
+                        stubPackages = newFilter
+                        newFilter
+                    }
+                    filter.packagePrefixes += packages.split(File.pathSeparatorChar)
+                }
+
+                "--stub-import-packages", "-stubimportpackages" -> {
+                    val packages = getValue(args, ++index)
+                    for (pkg in packages.split(File.pathSeparatorChar)) {
+                        mutableStubImportPackages.add(pkg)
+                        mutableHidePackages.add(pkg)
+                    }
+                }
+
+                "--skip-emit-packages" -> {
+                    val packages = getValue(args, ++index)
+                    mutableSkipEmitPackages += packages.split(File.pathSeparatorChar)
+                }
+
+                ARG_INPUT_API_JAR -> apiJar = stringToExistingFile(getValue(args, ++index))
+
+                ARG_EXTRACT_ANNOTATIONS -> externalAnnotations = stringToNewFile(getValue(args, ++index))
+
+                ARG_PREVIOUS_API -> previousApi = stringToExistingFile(getValue(args, ++index))
+
+                ARG_MIGRATE_NULLNESS -> migrateNulls = true
+
+                ARG_CHECK_COMPATIBILITY -> {
+                    checkCompatibility = true
+
+                    // Normally some compatibility changes are warnings but when you
+                    // explicitly check compatibility, turn them all into errors
+                    Errors.enforceCompatibility()
+                }
+
+                ARG_ANNOTATION_COVERAGE_STATS -> dumpAnnotationStatistics = true
+                ARG_ANNOTATION_COVERAGE_OF -> mutableAnnotationCoverageOf.add(
+                    stringToExistingFileOrDir(
+                        getValue(args, ++index)
+                    )
+                )
+
+                ARG_ERROR, "-error" -> Errors.setErrorLevel(getValue(args, ++index), Severity.ERROR)
+                ARG_WARNING, "-warning" -> Errors.setErrorLevel(getValue(args, ++index), Severity.WARNING)
+                ARG_LINT, "-lint" -> Errors.setErrorLevel(getValue(args, ++index), Severity.LINT)
+                ARG_HIDE, "-hide" -> Errors.setErrorLevel(getValue(args, ++index), Severity.HIDDEN)
+
+                ARG_WARNINGS_AS_ERRORS, "-werror" -> warningsAreErrors = true
+                ARG_LINTS_AS_ERRORS, "-lerror" -> lintsAreErrors = true
+
+                ARG_COLOR -> color = true
+                ARG_NO_COLOR -> color = false
+
+                ARG_OMIT_COMMON_PACKAGES, ARG_OMIT_COMMON_PACKAGES + "=yes" -> omitCommonPackages = true
+                ARG_OMIT_COMMON_PACKAGES + "=no" -> omitCommonPackages = false
+
+                ARG_SKIP_JAVA_IN_COVERAGE_REPORT -> omitRuntimePackageStats = true
+
+                ARG_UNHIDE_CLASSPATH_CLASSES -> hideClasspathClasses = false
+                ARG_ALLOW_REFERENCING_UNKNOWN_CLASSES -> allowReferencingUnknownClasses = true
+                ARG_NO_UNKNOWN_CLASSES -> noUnknownClasses = true
+
+                ARG_INCLUDE_DOC_ONLY -> includeDocOnly = true
+
+            // Annotation extraction flags
+                ARG_API_FILTER -> apiFilters.add(stringToExistingFile(getValue(args, ++index)))
+                ARG_RM_TYPEDEFS -> rmTypeDefs = stringToExistingDir(getValue(args, ++index))
+                ARG_TYPEDEF_FILE -> typedefFile = stringToNewFile(getValue(args, ++index))
+                ARG_HIDE_FILTERED -> hideFiltered = true
+                ARG_SKIP_CLASS_RETENTION -> skipClassRetention = true
+
+            // Extracting API levels
+                ARG_ANDROID_JAR_PATTERN -> {
+                    val list = androidJarPatterns ?: run {
+                        val list = arrayListOf<String>()
+                        androidJarPatterns = list
+                        list
+                    }
+                    list.add(getValue(args, ++index))
+                }
+                ARG_CURRENT_VERSION -> {
+                    currentApiLevel = Integer.parseInt(getValue(args, ++index))
+                    if (currentApiLevel <= 26) {
+                        throw OptionsException("Suspicious currentApi=$currentApiLevel, expected at least 27")
+                    }
+                }
+                ARG_CURRENT_CODENAME -> {
+                    currentCodeName = getValue(args, ++index)
+                }
+                ARG_CURRENT_JAR -> {
+                    currentJar = stringToExistingFile(getValue(args, ++index))
+                }
+                ARG_GENERATE_API_LEVELS -> {
+                    generateApiLevelXml = stringToNewFile(getValue(args, ++index))
+                }
+                ARG_APPLY_API_LEVELS -> {
+                    applyApiLevelsXml = stringToExistingFile(getValue(args, ++index))
+                }
+
+            // Unimplemented doclava1 flags (no arguments)
+                "-quiet" -> {
+                    unimplemented(arg)
+                }
+
+                "-android" -> { // partially implemented: Pick up the color hint, but there may be other implications
+                    color = true
+                    unimplemented(arg)
+                }
+
+                "-stubsourceonly" -> {
+                    /* noop */
+                }
+
+            // Unimplemented doclava1 flags (1 argument)
+                "-d" -> {
+                    unimplemented(arg)
+                    index++
+                }
+
+                "-encoding" -> {
+                    val value = getValue(args, ++index)
+                    if (value.toUpperCase() != "UTF-8") {
+                        throw OptionsException("$value: Only UTF-8 encoding is supported")
+                    }
+                }
+
+                "-source" -> {
+                    val value = getValue(args, ++index)
+                    if (value != "1.8") {
+                        throw OptionsException("$value: Only source 1.8 is supported")
+                    }
+                }
+
+            // Unimplemented doclava1 flags (2 arguments)
+                "-since" -> {
+                    unimplemented(arg)
+                    index += 2
+                }
+
+            // doclava1 doc-related flags: only supported here to make this command a drop-in
+            // replacement
+                "-referenceonly",
+                "-nodocs" -> {
+                    javadoc(arg)
+                }
+
+            // doclava1 flags with 1 argument
+                "-doclet",
+                "-docletpath",
+                "-templatedir",
+                "-htmldir",
+                "-knowntags",
+                "-resourcesdir",
+                "-resourcesoutdir",
+                "-overview" -> {
+                    javadoc(arg)
+                    index++
+                }
+
+            // doclava1 flags with two arguments
+                "-federate",
+                "-federationapi" -> {
+                    javadoc(arg)
+                    index += 2
+                }
+
+            // doclava1 flag with variable number of arguments; skip everything until next arg
+                "-hdf" -> {
+                    javadoc(arg)
+                    index++
+                    while (index < args.size) {
+                        if (args[index].startsWith("-")) {
+                            break
+                        }
+                        index++
+                    }
+                    index--
+                }
+
+                else -> {
+                    if (arg.startsWith("-J-") || arg.startsWith("-XD")) {
+                        // -J: mechanism to pass extra flags to javadoc, e.g.
+                        //    -J-XX:-OmitStackTraceInFastThrow
+                        // -XD: mechanism to set properties, e.g.
+                        //    -XDignore.symbol.file
+                        javadoc(arg)
+                    } else if (arg.startsWith(ARG_OUTPUT_KOTLIN_NULLS)) {
+                        outputKotlinStyleNulls = if (arg == ARG_OUTPUT_KOTLIN_NULLS) {
+                            true
+                        } else {
+                            yesNo(arg.substring(ARG_OUTPUT_KOTLIN_NULLS.length + 1))
+                        }
+                    } else if (arg.startsWith(ARG_INPUT_KOTLIN_NULLS)) {
+                        inputKotlinStyleNulls = if (arg == ARG_INPUT_KOTLIN_NULLS) {
+                            true
+                        } else {
+                            yesNo(arg.substring(ARG_INPUT_KOTLIN_NULLS.length + 1))
+                        }
+                    } else if (arg.startsWith(ARG_OMIT_COMMON_PACKAGES)) {
+                        omitCommonPackages = if (arg == ARG_OMIT_COMMON_PACKAGES) {
+                            true
+                        } else {
+                            yesNo(arg.substring(ARG_OMIT_COMMON_PACKAGES.length + 1))
+                        }
+                    } else if (arg.startsWith(ARGS_COMPAT_OUTPUT)) {
+                        compatOutput = if (arg == ARGS_COMPAT_OUTPUT)
+                            true
+                        else
+                            yesNo(arg.substring(ARGS_COMPAT_OUTPUT.length + 1))
+                    } else if (arg.startsWith("-")) {
+                        val usage = getUsage(includeHeader = false, colorize = color)
+                        throw OptionsException(stderr = "Invalid argument $arg\n\n$usage")
+                    } else {
+                        // All args that don't start with "-" are taken to be filenames
+                        mutableSources.addAll(stringToExistingFiles(arg))
+                    }
+                }
+            }
+
+            ++index
+        }
+
+        if (!apiFilters.isEmpty()) {
+            apiFilter = try {
+                val lines = Lists.newArrayList<String>()
+                for (file in apiFilters) {
+                    lines.addAll(Files.readLines(file, com.google.common.base.Charsets.UTF_8))
+                }
+                ApiDatabase(lines)
+            } catch (e: IOException) {
+                throw OptionsException("Could not open API database $apiFilters: ${e.localizedMessage}")
+            }
+        }
+
+        if (generateApiLevelXml != null) {
+            if (currentJar != null && currentApiLevel == -1 || currentJar == null && currentApiLevel != -1) {
+                throw OptionsException("You must specify both --current-jar and --current-version (or neither one)")
+            }
+            if (androidJarPatterns == null) {
+                androidJarPatterns = mutableListOf(
+                    "prebuilts/tools/common/api-versions/android-%/android.jar",
+                    "prebuilts/sdk/%/android.jar"
+                )
+            }
+            apiLevelJars = findAndroidJars(androidJarPatterns!!, currentApiLevel, currentCodeName, currentJar)
+        }
+
+        // If the caller has not explicitly requested that unannotated classes and
+        // members should be shown in the output then only show them if no annotations were provided.
+        if (!showUnannotated && showAnnotations.isEmpty()) {
+            showUnannotated = true
+        }
+
+        if (noUnknownClasses) {
+            allowReferencingUnknownClasses = false
+        }
+
+        checkFlagConsistency()
+    }
+
+    private fun findAndroidJars(
+        androidJarPatterns: List<String>, currentApiLevel: Int,
+        currentCodeName: String?, currentJar: File?
+    ): Array<File> {
+
+        @Suppress("NAME_SHADOWING")
+        val currentApiLevel = if (currentCodeName != null && "REL" != currentCodeName) {
+            currentApiLevel + 1
+        } else {
+            currentApiLevel
+        }
+
+        val apiLevelFiles = mutableListOf<File>()
+        apiLevelFiles.add(File("")) // api level 0: dummy
+        val minApi = 1
+
+        // Get all the android.jar. They are in platforms-#
+        var apiLevel = minApi - 1
+        while (true) {
+            apiLevel++
+            try {
+                var jar: File? = null
+                if (apiLevel == currentApiLevel) {
+                    jar = currentJar
+                }
+                if (jar == null) {
+                    jar = getAndroidJarFile(apiLevel, androidJarPatterns)
+                }
+                if (jar == null || !jar.isFile) {
+                    if (verbose) {
+                        stdout.println("Last API level found: ${apiLevel - 1}")
+                    }
+                    break
+                }
+                if (verbose) {
+                    stdout.println("Found API $apiLevel at ${jar.path}")
+                }
+                apiLevelFiles.add(jar)
+            } catch (e: IOException) {
+                e.printStackTrace()
+            }
+        }
+
+        return apiLevelFiles.toTypedArray()
+    }
+
+    private fun getAndroidJarFile(apiLevel: Int, patterns: List<String>): File? {
+        return patterns
+            .map { File(it.replace("%", Integer.toString(apiLevel))) }
+            .firstOrNull { it.isFile }
+    }
+
+    private fun yesNo(answer: String): Boolean {
+        return when (answer) {
+            "yes", "true", "enabled", "on" -> true
+            "no", "false", "disabled", "off" -> false
+            else -> throw OptionsException(stderr = "Unexpected $answer; expected yes or no")
+        }
+    }
+
+    /** Makes sure that the flag combinations make sense */
+    private fun checkFlagConsistency() {
+        if (checkCompatibility && previousApi == null) {
+            throw OptionsException(stderr = "$ARG_CHECK_COMPATIBILITY requires $ARG_PREVIOUS_API")
+        }
+
+        if (migrateNulls && previousApi == null) {
+            throw OptionsException(stderr = "$ARG_MIGRATE_NULLNESS requires $ARG_PREVIOUS_API")
+        }
+
+        if (apiJar != null && sources.isNotEmpty()) {
+            throw OptionsException(stderr = "Specify either $ARG_SOURCE_FILES or $ARG_INPUT_API_JAR, not both")
+        }
+
+        if (compatOutput && outputKotlinStyleNulls) {
+            throw OptionsException(
+                stderr = "$ARG_OUTPUT_KOTLIN_NULLS should not be combined with " +
+                        "$ARGS_COMPAT_OUTPUT=yes"
+            )
+        }
+
+//        if (stubsSourceList != null && stubsDir == null) {
+//            throw OptionsException(stderr = "$ARG_STUBS_SOURCE_LIST should only be used when $ARG_STUBS is set")
+//        }
+    }
+
+    private fun javadoc(arg: String) {
+        if (!alreadyWarned.add(arg)) {
+            return
+        }
+        if (!options.quiet) {
+            reporter.report(
+                Severity.WARNING, null as String?, "Ignoring javadoc-related doclava1 flag $arg",
+                color = color
+            )
+        }
+    }
+
+    private fun unimplemented(arg: String) {
+        if (!alreadyWarned.add(arg)) {
+            return
+        }
+        if (!options.quiet) {
+            val message = "Ignoring unimplemented doclava1 flag $arg" +
+                    when (arg) {
+                        "-encoding" -> " (UTF-8 assumed)"
+                        "-source" -> "  (1.8 assumed)"
+                        else -> ""
+                    }
+            reporter.report(Severity.WARNING, null as String?, message, color = color)
+
+        }
+    }
+
+    private fun helpAndQuit(colorize: Boolean = color) {
+        throw OptionsException(stdout = getUsage(colorize = colorize))
+    }
+
+    private fun getValue(args: Array<String>, index: Int): String {
+        if (index >= args.size) {
+            throw OptionsException("Missing argument for ${args[index - 1]}")
+        }
+        return args[index]
+    }
+
+    private fun stringToExistingDir(value: String): File {
+        val file = File(value)
+        if (!file.isDirectory) {
+            throw OptionsException("$file is not a directory")
+        }
+        return file
+    }
+
+    private fun stringToExistingDirs(value: String): List<File> {
+        val files = mutableListOf<File>()
+        for (path in value.split(File.pathSeparatorChar)) {
+            val file = File(path)
+            if (!file.isDirectory) {
+                throw OptionsException("$file is not a directory")
+            }
+            files.add(file)
+        }
+        return files
+    }
+
+    private fun stringToExistingDirsOrJars(value: String): List<File> {
+        val files = mutableListOf<File>()
+        for (path in value.split(File.pathSeparatorChar)) {
+            val file = File(path)
+            if (!file.isDirectory && !(file.path.endsWith(SdkConstants.DOT_JAR) && file.isFile)) {
+                throw OptionsException("$file is not a jar or directory")
+            }
+            files.add(file)
+        }
+        return files
+    }
+
+    private fun stringToExistingDirsOrFiles(value: String): List<File> {
+        val files = mutableListOf<File>()
+        for (path in value.split(File.pathSeparatorChar)) {
+            val file = File(path)
+            if (!file.exists()) {
+                throw OptionsException("$file does not exist")
+            }
+            files.add(file)
+        }
+        return files
+    }
+
+    private fun stringToExistingFile(value: String): File {
+        val file = File(value)
+        if (!file.isFile) {
+            throw OptionsException("$file is not a file")
+        }
+        return file
+    }
+
+    private fun stringToExistingFileOrDir(value: String): File {
+        val file = File(value)
+        if (!file.exists()) {
+            throw OptionsException("$file is not a file or directory")
+        }
+        return file
+    }
+
+    private fun stringToExistingFiles(value: String): List<File> {
+        val files = mutableListOf<File>()
+        value.split(File.pathSeparatorChar)
+            .map { File(it) }
+            .forEach { file ->
+                if (file.path.startsWith("@")) {
+                    // File list; files to be read are stored inside. SHOULD have been one per line
+                    // but sadly often uses spaces for separation too (so we split by whitespace,
+                    // which means you can't point to files in paths with spaces)
+                    val listFile = File(file.path.substring(1))
+                    if (!listFile.isFile) {
+                        throw OptionsException("$listFile is not a file")
+                    }
+                    val contents = Files.asCharSource(listFile, Charsets.UTF_8).read()
+                    val pathList = Splitter.on(CharMatcher.whitespace()).trimResults().omitEmptyStrings().split(
+                        contents
+                    )
+                    pathList.asSequence().map { File(it) }.forEach {
+                        if (!it.isFile) {
+                            throw OptionsException("$it is not a file")
+                        }
+                        files.add(it)
+                    }
+                } else {
+                    if (!file.isFile) {
+                        throw OptionsException("$file is not a file")
+                    }
+                    files.add(file)
+                }
+            }
+        return files
+    }
+
+    private fun stringToNewFile(value: String): File {
+        val output = File(value)
+
+        if (output.exists()) {
+            if (output.isDirectory) {
+                throw OptionsException("$output is a directory")
+            }
+            val deleted = output.delete()
+            if (!deleted) {
+                throw OptionsException("Could not delete previous version of $output")
+            }
+        } else if (output.parentFile != null && !output.parentFile.exists()) {
+            val ok = output.parentFile.mkdirs()
+            if (!ok) {
+                throw OptionsException("Could not create ${output.parentFile}")
+            }
+        }
+
+        return output
+    }
+
+    private fun stringToNewDir(value: String): File {
+        val output = File(value)
+
+        if (output.exists()) {
+            if (output.isDirectory) {
+                output.deleteRecursively()
+            }
+        } else if (output.parentFile != null && !output.parentFile.exists()) {
+            val ok = output.parentFile.mkdirs()
+            if (!ok) {
+                throw OptionsException("Could not create ${output.parentFile}")
+            }
+        }
+
+        return output
+    }
+
+    private fun getUsage(includeHeader: Boolean = true, colorize: Boolean = color): String {
+        val usage = StringWriter()
+        val printWriter = PrintWriter(usage)
+        usage(printWriter, includeHeader, colorize)
+        return usage.toString()
+    }
+
+    private fun usage(out: PrintWriter, includeHeader: Boolean = true, colorize: Boolean = color) {
+        if (includeHeader) {
+            out.println(wrap(HELP_PROLOGUE, MAX_LINE_WIDTH, ""))
+        }
+
+        if (colorize) {
+            out.println("Usage: ${colorized(PROGRAM_NAME, TerminalColor.BLUE)} <flags>")
+        } else {
+            out.println("Usage: $PROGRAM_NAME <flags>")
+        }
+
+        val args = arrayOf(
+            "", "\nGeneral:",
+            ARG_HELP, "This message.",
+            ARG_QUIET, "Only include vital output",
+            ARG_VERBOSE, "Include extra diagnostic output",
+            ARG_COLOR, "Attempt to colorize the output (defaults to true if \$TERM is xterm)",
+            ARG_NO_COLOR, "Do not attempt to colorize the output",
+
+            "", "\nAPI sources:",
+            ARG_SOURCE_FILES + " <files>", "A comma separated list of source files to be parsed. Can also be " +
+                    "@ followed by a path to a text file containing paths to the full set of files to parse.",
+
+            ARG_SOURCE_PATH + " <paths>", "One or more directories (separated by `${File.pathSeparator}`) " +
+                    "containing source files (within a package hierarchy)",
+
+            ARG_CLASS_PATH + " <paths>", "One or more directories or jars (separated by " +
+                    "`${File.pathSeparator}`) containing classes that should be on the classpath when parsing the " +
+                    "source files",
+
+            ARG_MERGE_ANNOTATIONS + " <file>", "An external annotations file (using IntelliJ's external " +
+                    "annotations database format) to merge and overlay the sources",
+
+            ARG_INPUT_API_JAR + " <file>", "A .jar file to read APIs from directly",
+
+            ARG_MANIFEST + " <file>", "A manifest file, used to for check permissions to cross check APIs",
+
+            ARG_HIDE_PACKAGE + " <package>", "Remove the given packages from the API even if they have not been " +
+                    "marked with @hide",
+
+            ARG_SHOW_ANNOTATION + " <annotation class>", "Include the given annotation in the API analysis",
+            ARG_SHOW_UNANNOTATED, "Include un-annotated public APIs in the signature file as well",
+
+            "", "\nExtracting Signature Files:",
+            // TODO: Document --show-annotation!
+            ARG_API + " <file>", "Generate a signature descriptor file",
+            ARG_PRIVATE_API + " <file>", "Generate a signature descriptor file listing the exact private APIs",
+            ARG_PRIVATE_DEX_API + " <file>", "Generate a DEX signature descriptor file listing the exact private APIs",
+            ARG_REMOVED_API + " <file>", "Generate a signature descriptor file for APIs that have been removed",
+            ARG_OUTPUT_KOTLIN_NULLS + "[=yes|no]", "Controls whether nullness annotations should be formatted as " +
+                    "in Kotlin (with \"?\" for nullable types, \"\" for non nullable types, and \"!\" for unknown. " +
+                    "The default is yes.",
+            ARGS_COMPAT_OUTPUT + "=[yes|no]", "Controls whether to keep signature files compatible with the " +
+                    "historical format (with its various quirks) or to generate the new format (which will also include " +
+                    "annotations that are part of the API, etc.)",
+            ARG_OMIT_COMMON_PACKAGES + "[=yes|no]", "Skip common package prefixes like java.lang.* and " +
+                    "kotlin.* in signature files, along with packages for well known annotations like @Nullable and " +
+                    "@NonNull.",
+
+            ARG_PROGUARD + " <file>", "Write a ProGuard keep file for the API",
+
+            "", "\nGenerating Stubs:",
+            ARG_STUBS + " <dir>", "Generate stub source files for the API",
+            ARG_EXCLUDE_ANNOTATIONS, "Exclude annotations such as @Nullable from the stub files",
+            ARG_STUBS_SOURCE_LIST + " <file>", "Write the list of generated stub files into the given source " +
+                    "list file",
+
+            "", "\nDiffs and Checks:",
+            ARG_PREVIOUS_API + " <signature file>", "A signature file for the previous version of this " +
+                    "API to apply diffs with",
+            ARG_INPUT_KOTLIN_NULLS + "[=yes|no]", "Whether the signature file being read should be " +
+                    "interpreted as having encoded its types using Kotlin style types: a suffix of \"?\" for nullable " +
+                    "types, no suffix for non nullable types, and \"!\" for unknown. The default is no.",
+            ARG_CHECK_COMPATIBILITY, "Check compatibility with the previous API",
+            ARG_MIGRATE_NULLNESS, "Compare nullness information with the previous API and mark newly " +
+                    "annotated APIs as under migration.",
+            ARG_WARNINGS_AS_ERRORS, "Promote all warnings to errors",
+            ARG_LINTS_AS_ERRORS, "Promote all API lint warnings to errors",
+            ARG_ERROR + " <id>", "Report issues of the given id as errors",
+            ARG_WARNING + " <id>", "Report issues of the given id as warnings",
+            ARG_LINT + " <id>", "Report issues of the given id as having lint-severity",
+            ARG_HIDE + " <id>", "Hide/skip issues of the given id",
+
+            "", "\nStatistics:",
+            ARG_ANNOTATION_COVERAGE_STATS, "Whether $PROGRAM_NAME should emit coverage statistics for " +
+                    "annotations, listing the percentage of the API that has been annotated with nullness information.",
+
+            ARG_ANNOTATION_COVERAGE_OF + " <paths>", "One or more jars (separated by `${File.pathSeparator}`) " +
+                    "containing existing apps that we want to measure annotation coverage statistics for. The set of " +
+                    "API usages in those apps are counted up and the most frequently used APIs that are missing " +
+                    "annotation metadata are listed in descending order.",
+
+            ARG_SKIP_JAVA_IN_COVERAGE_REPORT, "In the coverage annotation report, skip java.** and kotlin.** to " +
+                    "narrow the focus down to the Android framework APIs.",
+
+            "", "\nExtracting Annotations:",
+            ARG_EXTRACT_ANNOTATIONS + " <zipfile>", "Extracts annotations from the source files and writes them " +
+                    "into the given zip file",
+
+            ARG_API_FILTER + " <file>", "Applies the given signature file as a filter (which means no classes," +
+                    "methods or fields not found in the filter will be included.)",
+            ARG_HIDE_FILTERED, "Omit listing APIs that were skipped because of the $ARG_API_FILTER",
+
+            ARG_SKIP_CLASS_RETENTION, "Do not extract annotations that have class file retention",
+            ARG_RM_TYPEDEFS, "Delete all the typedef .class files",
+            ARG_TYPEDEF_FILE + " <file>", "Writes an typedef annotation class names into the given file",
+
+            "", "\nInjecting API Levels:",
+            ARG_APPLY_API_LEVELS + " <api-versions.xml>", "Reads an XML file containing API level descriptions " +
+                    "and merges the information into the documentation",
+
+            "", "\nExtracting API Levels:",
+            ARG_GENERATE_API_LEVELS + " <xmlfile>",
+            "Reads android.jar SDK files and generates an XML file recording " +
+                    "the API level for each class, method and field",
+            ARG_ANDROID_JAR_PATTERN + " <pattern>", "Patterns to use to locate Android JAR files. The default " +
+                    "is \$ANDROID_HOME/platforms/android-%/android.jar.",
+            ARG_CURRENT_VERSION, "Sets the current API level of the current source code",
+            ARG_CURRENT_CODENAME, "Sets the code name for the current source code",
+            ARG_CURRENT_JAR, "Points to the current API jar, if any"
+        )
+
+        var argWidth = 0
+        var i = 0
+        while (i < args.size) {
+            val arg = args[i]
+            argWidth = Math.max(argWidth, arg.length)
+            i += 2
+        }
+        argWidth += 2
+        val sb = StringBuilder(20)
+        for (indent in 0 until argWidth) {
+            sb.append(' ')
+        }
+        val indent = sb.toString()
+        val formatString = "%1$-" + argWidth + "s%2\$s"
+
+        i = 0
+        while (i < args.size) {
+            val arg = args[i]
+            val description = args[i + 1]
+            if (arg.isEmpty()) {
+                if (colorize) {
+                    out.println(colorized(description, TerminalColor.YELLOW))
+                } else {
+                    out.println(description)
+                }
+            } else {
+                if (colorize) {
+                    val colorArg = bold(arg)
+                    val invisibleChars = colorArg.length - arg.length
+                    // +invisibleChars: the extra chars in the above are counted but don't contribute to width
+                    // so allow more space
+                    val colorFormatString = "%1$-" + (argWidth + invisibleChars) + "s%2\$s"
+
+                    out.print(
+                        SdkUtils2.wrap(
+                            String.format(colorFormatString, colorArg, description),
+                            MAX_LINE_WIDTH + invisibleChars, MAX_LINE_WIDTH, indent
+                        )
+                    )
+                } else {
+                    out.print(
+                        SdkUtils2.wrap(
+                            String.format(formatString, arg, description),
+                            MAX_LINE_WIDTH, indent
+                        )
+                    )
+                }
+            }
+            i += 2
+        }
+    }
+
+    class OptionsException(
+        val stderr: String = "",
+        val stdout: String = "",
+        val exitCode: Int = if (stderr.isBlank()) 0 else -1
+    ) : RuntimeException(stdout + stderr)
+}
diff --git a/src/main/java/com/android/tools/metalava/PackageFilter.kt b/src/main/java/com/android/tools/metalava/PackageFilter.kt
new file mode 100644
index 0000000..089265d
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/PackageFilter.kt
@@ -0,0 +1,26 @@
+package com.android.tools.metalava
+
+import com.android.tools.metalava.model.PackageItem
+
+class PackageFilter(
+    val packagePrefixes: MutableList<String> = mutableListOf()
+) {
+
+    fun matches(qualifiedName: String): Boolean {
+        for (prefix in packagePrefixes) {
+            if (qualifiedName.startsWith(prefix)) {
+                if (qualifiedName == prefix) {
+                    return true
+                } else if (qualifiedName[prefix.length] == '.') {
+                    return true
+                }
+            }
+        }
+
+        return false
+    }
+
+    fun matches(packageItem: PackageItem): Boolean {
+        return matches(packageItem.qualifiedName())
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/ProguardWriter.kt b/src/main/java/com/android/tools/metalava/ProguardWriter.kt
new file mode 100644
index 0000000..f43a9ed
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/ProguardWriter.kt
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava
+
+import com.android.tools.metalava.model.ClassItem
+import com.android.tools.metalava.model.ConstructorItem
+import com.android.tools.metalava.model.FieldItem
+import com.android.tools.metalava.model.Item
+import com.android.tools.metalava.model.MethodItem
+import com.android.tools.metalava.model.ParameterItem
+import com.android.tools.metalava.model.TypeItem
+import com.android.tools.metalava.model.visitors.ApiVisitor
+import java.io.PrintWriter
+import java.util.function.Predicate
+
+class ProguardWriter(
+    private val writer: PrintWriter,
+    filterEmit: Predicate<Item>,
+    filterReference: Predicate<Item>
+) : ApiVisitor(
+    visitConstructorsAsMethods = false,
+    nestInnerClasses = false,
+    inlineInheritedFields = true,
+    filterEmit = filterEmit,
+    filterReference = filterReference
+) {
+
+    override fun visitClass(cls: ClassItem) {
+        writer.print("-keep class ")
+        writer.print(cls.qualifiedNameWithDollarInnerClasses())
+        writer.print(" {\n")
+    }
+
+    override fun afterVisitClass(cls: ClassItem) {
+        writer.print("}\n")
+    }
+
+    override fun visitConstructor(constructor: ConstructorItem) {
+        writer.print("    ")
+        writer.print("<init>")
+
+        writeParametersKeepList(constructor.parameters())
+        writer.print(";\n")
+    }
+
+    override fun visitMethod(method: MethodItem) {
+        writer.print("    ")
+        val modifiers = method.modifiers
+        when {
+            modifiers.isPublic() -> writer.write("public ")
+            modifiers.isProtected() -> writer.write("protected ")
+            modifiers.isPrivate() -> writer.write("private ")
+        }
+
+        if (modifiers.isStatic()) {
+            writer.print("static ")
+        }
+        if (modifiers.isAbstract()) {
+            writer.print("abstract ")
+        }
+        if (modifiers.isSynchronized()) {
+            writer.print("synchronized ")
+        }
+
+        writer.print(getCleanTypeName(method.returnType()))
+        writer.print(" ")
+        writer.print(method.name())
+
+        writeParametersKeepList(method.parameters())
+
+        writer.print(";\n")
+    }
+
+    private fun writeParametersKeepList(params: List<ParameterItem>) {
+        writer.print("(")
+
+        for (pi in params) {
+            if (pi !== params[0]) {
+                writer.print(", ")
+            }
+            writer.print(getCleanTypeName(pi.type()))
+        }
+
+        writer.print(")")
+    }
+
+    override fun visitField(field: FieldItem) {
+        writer.print("    ")
+
+        val modifiers = field.modifiers
+        when {
+            modifiers.isPublic() -> writer.write("public ")
+            modifiers.isProtected() -> writer.write("protected ")
+            modifiers.isPrivate() -> writer.write("private ")
+        }
+
+        if (modifiers.isStatic()) {
+            writer.print("static ")
+        }
+        if (modifiers.isTransient()) {
+            writer.print("transient ")
+        }
+        if (modifiers.isVolatile()) {
+            writer.print("volatile ")
+        }
+
+        writer.print(getCleanTypeName(field.type()))
+
+        writer.print(" ")
+        writer.print(field.name())
+
+        writer.print(";\n")
+    }
+
+    private fun getCleanTypeName(t: TypeItem?): String {
+        t ?: return ""
+        val cls = t.asClass() ?: return t.toSimpleType()
+        return cls.qualifiedNameWithDollarInnerClasses()
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/Reporter.kt b/src/main/java/com/android/tools/metalava/Reporter.kt
new file mode 100644
index 0000000..189de9e
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/Reporter.kt
@@ -0,0 +1,279 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava
+
+import com.android.SdkConstants.ATTR_VALUE
+import com.android.tools.metalava.Severity.ERROR
+import com.android.tools.metalava.Severity.HIDDEN
+import com.android.tools.metalava.Severity.INHERIT
+import com.android.tools.metalava.Severity.LINT
+import com.android.tools.metalava.Severity.WARNING
+import com.android.tools.metalava.doclava1.Errors
+import com.android.tools.metalava.model.AnnotationArrayAttributeValue
+import com.android.tools.metalava.model.Item
+import com.android.tools.metalava.model.psi.PsiItem
+import com.android.tools.metalava.model.text.TextItem
+import com.intellij.openapi.util.TextRange
+import com.intellij.openapi.vfs.StandardFileSystems
+import com.intellij.openapi.vfs.VfsUtilCore
+import com.intellij.openapi.vfs.VirtualFile
+import com.intellij.psi.PsiCompiledElement
+import com.intellij.psi.PsiElement
+import com.intellij.psi.impl.light.LightElement
+import java.io.File
+
+var reporter = Reporter()
+
+enum class Severity(private val displayName: String) {
+    INHERIT("inherit"),
+
+    HIDDEN("hidden"),
+
+    /**
+     * Lint level means that we encountered inconsistent or broken documentation.
+     * These should be resolved, but don't impact API compatibility.
+     */
+    LINT("lint"),
+
+    /**
+     * Warning level means that we encountered some incompatible or inconsistent
+     * API change. These must be resolved to preserve API compatibility.
+     */
+    WARNING("warning"),
+
+    /**
+     * Error level means that we encountered severe trouble and were unable to
+     * output the requested documentation.
+     */
+    ERROR("error");
+
+    override fun toString(): String = displayName
+}
+
+open class Reporter(private val rootFolder: File? = null) {
+    var hasErrors = false
+
+    fun error(item: Item?, message: String, id: Errors.Error? = null) {
+        error(item?.psi(), message, id)
+    }
+
+    fun warning(item: Item?, message: String, id: Errors.Error? = null) {
+        warning(item?.psi(), message, id)
+    }
+
+    fun error(element: PsiElement?, message: String, id: Errors.Error? = null) {
+        // Using lowercase since that's the convention doclava1 is using
+        report(ERROR, element, message, id)
+    }
+
+    fun warning(element: PsiElement?, message: String, id: Errors.Error? = null) {
+        report(WARNING, element, message, id)
+    }
+
+    fun report(id: Errors.Error, element: PsiElement?, message: String) {
+        report(id.level, element, message, id)
+    }
+
+    fun report(id: Errors.Error, file: File?, message: String) {
+        report(id.level, file?.path, message, id)
+    }
+
+    fun report(id: Errors.Error, item: Item?, message: String) {
+        if (isSuppressed(id, item)) {
+            return
+        }
+
+        when (item) {
+            is PsiItem -> report(id.level, item.psi(), message, id)
+            is TextItem -> report(id.level, (item as? TextItem)?.position.toString(), message, id)
+            else -> report(id.level, "<unknown location>", message, id)
+        }
+    }
+
+    private fun isSuppressed(id: Errors.Error, item: Item?): Boolean {
+        item ?: return false
+
+        if (id.level == LINT || id.level == WARNING) {
+            val id1 = "Doclava${id.code}"
+            val id2 = id.name
+            val annotation = item.modifiers.findAnnotation("android.annotation.SuppressLint")
+            if (annotation != null) {
+                val attribute = annotation.findAttribute(ATTR_VALUE)
+                if (attribute != null) {
+                    val value = attribute.value
+                    if (value is AnnotationArrayAttributeValue) {
+                        // Example: @SuppressLint({"DocLava1", "DocLava2"})
+                        for (innerValue in value.values) {
+                            val string = innerValue.value()
+                            if (id1 == string || id2 != null && id2 == string) {
+                                return true
+                            }
+                        }
+                    } else {
+                        // Example: @SuppressLint("DocLava1")
+                        val string = value.value()
+                        if (id1 == string || id2 != null && id2 == string) {
+                            return true
+                        }
+                    }
+                }
+            }
+        }
+
+        return false
+    }
+
+    private fun getTextRange(element: PsiElement): TextRange? {
+        var range: TextRange? = null
+
+        if (element is PsiCompiledElement) {
+            if (element is LightElement) {
+                range = (element as PsiElement).textRange
+            }
+            if (range == null || TextRange.EMPTY_RANGE == range) {
+                return null
+            }
+        } else {
+            range = element.textRange
+        }
+
+        return range
+    }
+
+    private fun elementToLocation(element: PsiElement?): String? {
+        element ?: return null
+        val psiFile = element.containingFile ?: return null
+        val virtualFile = psiFile.virtualFile ?: return null
+        val file = VfsUtilCore.virtualToIoFile(virtualFile)
+
+        val path =
+            if (rootFolder != null) {
+                val root: VirtualFile? = StandardFileSystems.local().findFileByPath(rootFolder.path)
+                if (root != null) VfsUtilCore.getRelativePath(virtualFile, root) else file.path
+            } else {
+                file.path
+            }
+
+        val range = getTextRange(element)
+        return if (range == null) {
+            // No source offsets, just use filename
+            path
+        } else {
+            val lineNumber = getLineNumber(psiFile.text, range.startOffset)
+            path + ":" + lineNumber
+        }
+    }
+
+    private fun getLineNumber(text: String, offset: Int): Int {
+        var line = 0
+        var curr = offset
+        val length = text.length
+        while (curr < length) {
+            if (text[curr++] == '\n') {
+                line++
+            }
+        }
+        return line
+    }
+
+    open fun report(severity: Severity, element: PsiElement?, message: String, id: Errors.Error? = null) {
+        if (severity == HIDDEN) {
+            return
+        }
+
+        report(severity, elementToLocation(element), message, id)
+    }
+
+    open fun report(
+        severity: Severity, location: String?, message: String, id: Errors.Error? = null,
+        color: Boolean = options.color
+    ) {
+        if (severity == HIDDEN) {
+            return
+        }
+
+        val effectiveSeverity =
+            if (severity == LINT && options.lintsAreErrors)
+                ERROR
+            else if (severity == WARNING && options.warningsAreErrors) {
+                ERROR
+            } else {
+                severity
+            }
+
+        if (severity == ERROR) {
+            hasErrors = true
+        }
+
+        val sb = StringBuilder(100)
+
+        if (color) {
+            sb.append(terminalAttributes(bold = true))
+            location?.let { sb.append(it).append(": ") }
+            when (effectiveSeverity) {
+                LINT -> sb.append(terminalAttributes(foreground = TerminalColor.CYAN)).append("lint: ")
+                WARNING -> sb.append(terminalAttributes(foreground = TerminalColor.YELLOW)).append("warning: ")
+                ERROR -> sb.append(terminalAttributes(foreground = TerminalColor.RED)).append("error: ")
+                INHERIT, HIDDEN -> {
+                }
+            }
+            sb.append(resetTerminal())
+            sb.append(message)
+            id?.let { sb.append(" [").append(if (it.name != null) it.name else it.code).append("]") }
+        } else {
+            location?.let { sb.append(it).append(": ") }
+            if (compatibility.oldErrorOutputFormat) {
+                // according to doclava1 there are some people or tools parsing old format
+                when (effectiveSeverity) {
+                    LINT -> sb.append("lint ")
+                    WARNING -> sb.append("warning ")
+                    ERROR -> sb.append("error ")
+                    INHERIT, HIDDEN -> {
+                    }
+                }
+                id?.let { sb.append(if (it.name != null) it.name else it.code).append(": ") }
+                sb.append(message)
+            } else {
+                when (effectiveSeverity) {
+                    LINT -> sb.append("lint: ")
+                    WARNING -> sb.append("warning: ")
+                    ERROR -> sb.append("error: ")
+                    INHERIT, HIDDEN -> {
+                    }
+                }
+                sb.append(message)
+                id?.let {
+                    sb.append(" [")
+                    if (it.name != null) {
+                        sb.append(it.name).append(":")
+                    }
+                    sb.append(it.code)
+                    sb.append("]")
+                }
+            }
+        }
+        print(sb.toString())
+    }
+
+    open fun print(message: String) {
+        options.stdout.println()
+        options.stdout.print(message.trim())
+        options.stdout.flush()
+    }
+
+    fun hasErrors(): Boolean = hasErrors
+}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/SignatureWriter.kt b/src/main/java/com/android/tools/metalava/SignatureWriter.kt
new file mode 100644
index 0000000..49f362c
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/SignatureWriter.kt
@@ -0,0 +1,289 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava
+
+import com.android.tools.metalava.model.ClassItem
+import com.android.tools.metalava.model.ConstructorItem
+import com.android.tools.metalava.model.FieldItem
+import com.android.tools.metalava.model.Item
+import com.android.tools.metalava.model.MethodItem
+import com.android.tools.metalava.model.ModifierList
+import com.android.tools.metalava.model.PackageItem
+import com.android.tools.metalava.model.TypeItem
+import com.android.tools.metalava.model.visitors.ApiVisitor
+import java.io.PrintWriter
+import java.util.function.Predicate
+
+class SignatureWriter(
+    private val writer: PrintWriter,
+    filterEmit: Predicate<Item>,
+    filterReference: Predicate<Item>,
+    private val preFiltered: Boolean
+) : ApiVisitor(
+    visitConstructorsAsMethods = false,
+    nestInnerClasses = false,
+    inlineInheritedFields = true,
+    methodComparator = MethodItem.comparator,
+    fieldComparator = FieldItem.comparator,
+    filterEmit = filterEmit,
+    filterReference = filterReference
+) {
+
+    override fun visitPackage(pkg: PackageItem) {
+        writer.print("package ${pkg.qualifiedName()} {\n\n")
+    }
+
+    override fun afterVisitPackage(pkg: PackageItem) {
+        writer.print("}\n\n")
+    }
+
+    override fun visitConstructor(constructor: ConstructorItem) {
+        writer.print("    ctor ")
+        writeModifiers(constructor)
+        // Note - we don't write out the type parameter list (constructor.typeParameterList()) in signature files!
+        //writeTypeParameterList(constructor.typeParameterList(), addSpace = true)
+        writer.print(constructor.containingClass().fullName())
+        writeParameterList(constructor)
+        writeThrowsList(constructor)
+        writer.print(";\n")
+    }
+
+    override fun visitField(field: FieldItem) {
+        val name = if (field.isEnumConstant()) "enum_constant" else "field"
+        writer.print("    ")
+        writer.print(name)
+        writer.print(" ")
+        writeModifiers(field)
+        writeType(field.type(), field.modifiers)
+        writer.print(' ')
+        writer.print(field.name())
+        field.writeValueWithSemicolon(writer, allowDefaultValue = false, requireInitialValue = false)
+        writer.print("\n")
+    }
+
+    override fun visitMethod(method: MethodItem) {
+        if (compatibility.skipAnnotationInstanceMethods && method.containingClass().isAnnotationType() &&
+            !method.modifiers.isStatic()
+        ) {
+            return
+        }
+
+        if (compatibility.skipInheritedInterfaceMethods && method.inheritedInterfaceMethod) {
+            return
+        }
+
+        writer.print("    method ")
+        writeModifiers(method)
+        writeTypeParameterList(method.typeParameterList(), addSpace = true)
+
+        writeType(method.returnType(), method.modifiers)
+        writer.print(' ')
+        writer.print(method.name())
+        writeParameterList(method)
+        writeThrowsList(method)
+        writer.print(";\n")
+    }
+
+    override fun visitClass(cls: ClassItem) {
+        writer.print("  ")
+
+        if (compatibility.extraSpaceForEmptyModifiers && cls.isPackagePrivate && cls.isPackagePrivate) {
+            writer.print(" ")
+        }
+
+        writeModifiers(cls)
+
+        if (cls.isAnnotationType()) {
+            if (compatibility.classForAnnotations) {
+                // doclava incorrectly treats annotations (such as TargetApi) as an abstract class instead
+                // of an @interface!
+                //
+                // Example:
+                //   public abstract class SuppressLint implements java.lang.annotation.Annotation { }
+                writer.print("class")
+            } else {
+                writer.print("@interface")
+            }
+        } else if (cls.isInterface()) {
+            writer.print("interface")
+        } else if (!compatibility.classForEnums && cls.isEnum()) { // compat mode calls enums "class" instead
+            writer.print("enum")
+        } else {
+            writer.print("class")
+        }
+        writer.print(" ")
+        writer.print(cls.fullName())
+        writeTypeParameterList(cls.typeParameterList(), addSpace = false)
+        writeSuperClassStatement(cls)
+        writeInterfaceList(cls)
+
+        writer.print(" {\n")
+    }
+
+    override fun afterVisitClass(cls: ClassItem) {
+        writer.print("  }\n\n")
+    }
+
+    private fun writeModifiers(item: Item) {
+        ModifierList.write(
+            writer = writer,
+            modifiers = item.modifiers,
+            item = item,
+            includeDeprecated = true,
+            includeAnnotations = compatibility.annotationsInSignatures,
+            skipNullnessAnnotations = options.outputKotlinStyleNulls,
+            omitCommonPackages = options.omitCommonPackages
+        )
+    }
+
+    private fun writeSuperClassStatement(cls: ClassItem) {
+        if (!compatibility.classForEnums && cls.isEnum() || cls.isAnnotationType()) {
+            return
+        }
+
+        if (cls.isInterface() && compatibility.extendsForInterfaceSuperClass) {
+            // Written in the interface section instead
+            return
+        }
+
+        val superClass = if (preFiltered)
+            cls.superClassType()
+        else
+            cls.filteredSuperClassType(filterReference)
+        if (superClass != null && !superClass.isJavaLangObject()) {
+            val superClassString =
+                superClass.toTypeString(erased = compatibility.omitTypeParametersInInterfaces)
+            writer.print(" extends ")
+            writer.print(superClassString)
+        }
+    }
+
+    private fun writeInterfaceList(cls: ClassItem) {
+        if (cls.isAnnotationType()) {
+            if (compatibility.classForAnnotations) {
+                writer.print(" implements java.lang.annotation.Annotation")
+            }
+            return
+        }
+        val isInterface = cls.isInterface()
+
+        val interfaces = if (preFiltered)
+            cls.interfaceTypes().asSequence()
+        else
+            cls.filteredInterfaceTypes(filterReference).asSequence()
+        val all: Sequence<TypeItem> = if (isInterface && compatibility.extendsForInterfaceSuperClass) {
+            val superClassType = cls.superClassType()
+            if (superClassType != null && !superClassType.isJavaLangObject()) {
+                interfaces.plus(sequenceOf(superClassType))
+            } else {
+                interfaces
+            }
+        } else {
+            interfaces
+        }
+
+        if (all.any()) {
+            val label = if (isInterface && !compatibility.extendsForInterfaceSuperClass) " extends" else " implements"
+            writer.print(label)
+
+            all.sortedWith(TypeItem.comparator).forEach { item ->
+                writer.print(" ")
+                writer.print(item.toTypeString(erased = compatibility.omitTypeParametersInInterfaces))
+            }
+        }
+    }
+
+    private fun writeTypeParameterList(typeList: String?, addSpace: Boolean) {
+        if (typeList != null) {
+            writer.print(typeList)
+            if (addSpace) {
+                writer.print(' ')
+            }
+        }
+    }
+
+    private fun writeParameterList(method: MethodItem) {
+        writer.print("(")
+        val emitParameterNames = compatibility.parameterNames
+        method.parameters().asSequence().forEachIndexed { i, parameter ->
+            if (i > 0) {
+                writer.print(", ")
+            }
+            writeModifiers(parameter)
+            writeType(parameter.type(), parameter.modifiers)
+            if (emitParameterNames) {
+                val name = parameter.publicName()
+                if (name != null) {
+                    writer.print(" ")
+                    writer.print(name)
+                }
+            }
+        }
+        writer.print(")")
+    }
+
+    private fun writeType(type: TypeItem?, modifiers: ModifierList) {
+        type ?: return
+
+        var typeString = type.toTypeString(
+            erased = false,
+            outerAnnotations = false,
+            innerAnnotations = compatibility.annotationsInSignatures
+        )
+
+        // Strip java.lang. prefix?
+        if (options.omitCommonPackages) {
+            typeString = TypeItem.shortenTypes(typeString)
+        }
+
+        writer.print(typeString)
+
+        if (options.outputKotlinStyleNulls && !type.primitive) {
+            var nullable: Boolean? = null
+            for (annotation in modifiers.annotations()) {
+                if (annotation.isNullable()) {
+                    nullable = true
+                } else if (annotation.isNonNull()) {
+                    nullable = false
+                }
+            }
+            when (nullable) {
+                null -> writer.write("!")
+                true -> writer.write("?")
+            // else: non-null: nothing to write
+            }
+        }
+    }
+
+    private fun writeThrowsList(method: MethodItem) {
+        val throws = if (preFiltered)
+            method.throwsTypes().asSequence().sortedWith(ClassItem.fullNameComparator)
+        else
+            method.throwsTypes().asSequence()
+                .filter { filterReference.test(it) }
+                .sortedWith(ClassItem.fullNameComparator)
+        if (throws.any()) {
+            writer.print(" throws ")
+            throws.asSequence().forEachIndexed { i, type ->
+                if (i > 0) {
+                    writer.print(", ")
+                }
+                writer.print(type.qualifiedName())
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/StubWriter.kt b/src/main/java/com/android/tools/metalava/StubWriter.kt
new file mode 100644
index 0000000..0cf793e
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/StubWriter.kt
@@ -0,0 +1,516 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava
+
+import com.android.tools.metalava.doclava1.ApiPredicate
+import com.android.tools.metalava.doclava1.Errors
+import com.android.tools.metalava.doclava1.FilterPredicate
+import com.android.tools.metalava.model.ClassItem
+import com.android.tools.metalava.model.Codebase
+import com.android.tools.metalava.model.ConstructorItem
+import com.android.tools.metalava.model.FieldItem
+import com.android.tools.metalava.model.Item
+import com.android.tools.metalava.model.MethodItem
+import com.android.tools.metalava.model.ModifierList
+import com.android.tools.metalava.model.PackageItem
+import com.android.tools.metalava.model.psi.PsiClassItem
+import com.android.tools.metalava.model.psi.trimDocIndent
+import com.android.tools.metalava.model.visitors.ApiVisitor
+import com.google.common.io.Files
+import java.io.BufferedWriter
+import java.io.File
+import java.io.FileWriter
+import java.io.IOException
+import java.io.PrintWriter
+import kotlin.text.Charsets.UTF_8
+
+class StubWriter(
+    codebase: Codebase,
+    private val stubsDir: File,
+    private val generateAnnotations: Boolean = false,
+    private val preFiltered: Boolean = true
+) : ApiVisitor(
+    visitConstructorsAsMethods = false,
+    nestInnerClasses = true,
+    inlineInheritedFields = true,
+    fieldComparator = FieldItem.comparator,
+    // Methods are by default sorted in source order in stubs, to encourage methods
+    // that are near each other in the source to show up near each other in the documentation
+    methodComparator = MethodItem.sourceOrderComparator,
+    filterEmit = FilterPredicate(ApiPredicate(codebase)),
+    filterReference = ApiPredicate(codebase, ignoreShown = true)
+) {
+
+    private val sourceList = StringBuilder(20000)
+
+    override fun include(cls: ClassItem): Boolean {
+        val filter = options.stubPackages
+        if (filter != null && !filter.matches(cls.containingPackage())) {
+            return false
+        }
+        return super.include(cls)
+    }
+
+    /** Writes a source file list of the generated stubs */
+    fun writeSourceList(target: File, root: File?) {
+        target.parentFile?.mkdirs()
+        val contents = if (root != null) {
+            val path = root.path.replace('\\', '/')
+            sourceList.toString().replace(path, "")
+        } else {
+            sourceList.toString()
+        }
+        Files.asCharSink(target, UTF_8).write(contents)
+    }
+
+    private fun startFile(sourceFile: File) {
+        if (sourceList.isNotEmpty()) {
+            sourceList.append(' ')
+        }
+        sourceList.append(sourceFile.path.replace('\\', '/'))
+    }
+
+    override fun visitPackage(pkg: PackageItem) {
+        getPackageDir(pkg, create = true)
+
+        // TODO: Write package annotations into package-info.java!
+        // TODO: Write package.html, if applicable
+    }
+
+    private fun getPackageDir(packageItem: PackageItem, create: Boolean = true): File {
+        val relative = packageItem.qualifiedName().replace('.', File.separatorChar)
+        val dir = File(stubsDir, relative)
+        if (create && !dir.isDirectory) {
+            val ok = dir.mkdirs()
+            if (!ok) {
+                throw IOException("Could not create $dir")
+            }
+        }
+
+        return dir
+    }
+
+    private fun getClassFile(classItem: ClassItem): File {
+        assert(classItem.containingClass() == null, { "Should only be called on top level classes" })
+        // TODO: Look up compilation unit language
+        return File(getPackageDir(classItem.containingPackage()), "${classItem.simpleName()}.java")
+    }
+
+    /**
+     * Between top level class files the [writer] field doesn't point to a real file; it
+     * points to this writer, which redirects to the error output. Nothing should be written
+     * to the writer at that time.
+     */
+    private var errorWriter = PrintWriter(options.stderr)
+
+    /** The writer to write the stubs file to */
+    private var writer: PrintWriter = errorWriter
+
+    override fun visitClass(cls: ClassItem) {
+        if (cls.isTopLevelClass()) {
+            val sourceFile = getClassFile(cls)
+            writer = try {
+                PrintWriter(BufferedWriter(FileWriter(sourceFile)))
+            } catch (e: IOException) {
+                reporter.report(Errors.IO_ERROR, sourceFile, "Cannot open file for write.")
+                errorWriter
+            }
+
+            startFile(sourceFile)
+
+            // Copyright statements from the original file?
+            val compilationUnit = cls.getCompilationUnit()
+            compilationUnit?.getHeaderComments()?.let { writer.println(it) }
+
+            val qualifiedName = cls.containingPackage().qualifiedName()
+            if (qualifiedName.isNotBlank()) {
+                writer.println("package $qualifiedName;")
+                writer.println()
+            }
+
+            compilationUnit?.getImportStatements(filterReference)?.let {
+                for (importedClass in it) {
+                    writer.println("import $importedClass;")
+                }
+                writer.println()
+            }
+        }
+
+        appendDocumentation(cls, writer)
+
+        // "ALL" doesn't do it; compiler still warns unless you actually explicitly list "unchecked"
+        writer.println("@SuppressWarnings({\"unchecked\", \"deprecation\", \"all\"})")
+
+        // Need to filter out abstract from the modifiers list and turn it
+        // into a concrete method to make the stub compile
+        val removeAbstract = cls.modifiers.isAbstract() && (cls.isEnum() || cls.isAnnotationType())
+
+        appendModifiers(cls, removeAbstract)
+
+        when {
+            cls.isAnnotationType() -> writer.print("@interface")
+            cls.isInterface() -> writer.print("interface")
+            cls.isEnum() -> writer.print("enum")
+            else -> writer.print("class")
+        }
+
+        writer.print(" ")
+        writer.print(cls.simpleName())
+
+        generateTypeParameterList(typeList = cls.typeParameterList(), addSpace = false)
+        generateSuperClassStatement(cls)
+        generateInterfaceList(cls)
+
+        writer.print(" {\n")
+
+        if (cls.isEnum()) {
+            var first = true
+            // Enums should preserve the original source order, not alphabetical etc sort
+            for (field in cls.fields().sortedBy { it.sortingRank }) {
+                if (field.isEnumConstant()) {
+                    if (first) {
+                        first = false
+                    } else {
+                        writer.write(", ")
+                    }
+                    writer.write(field.name())
+                }
+            }
+            writer.println(";")
+        }
+
+        generateMissingConstructors(cls)
+    }
+
+    private fun appendDocumentation(item: Item, writer: PrintWriter) {
+        val documentation = item.fullyQualifiedDocumentation()
+        if (documentation.isNotBlank()) {
+            val trimmed = trimDocIndent(documentation)
+            writer.println(trimmed)
+            writer.println()
+        }
+    }
+
+    override fun afterVisitClass(cls: ClassItem) {
+        writer.print("}\n\n")
+
+        if (cls.isTopLevelClass()) {
+            writer.flush()
+            writer.close()
+            writer = errorWriter
+        }
+    }
+
+    private fun appendModifiers(
+        item: Item,
+        removeAbstract: Boolean,
+        removeFinal: Boolean = false,
+        addPublic: Boolean = false
+    ) {
+        appendModifiers(item, item.modifiers, removeAbstract, removeFinal, addPublic)
+    }
+
+    private fun appendModifiers(
+        item: Item,
+        modifiers: ModifierList,
+        removeAbstract: Boolean,
+        removeFinal: Boolean = false,
+        addPublic: Boolean = false
+    ) {
+        if (item.deprecated) {
+            writer.write("@Deprecated ")
+        }
+
+        ModifierList.write(
+            writer, modifiers, item, removeAbstract = removeAbstract, removeFinal = removeFinal,
+            addPublic = addPublic, includeAnnotations = generateAnnotations
+        )
+    }
+
+    private fun generateSuperClassStatement(cls: ClassItem) {
+        if (cls.isEnum() || cls.isAnnotationType()) {
+            // No extends statement for enums and annotations; it's implied by the "enum" and "@interface" keywords
+            return
+        }
+
+        val superClass = if (preFiltered)
+            cls.superClassType()
+        else
+            cls.filteredSuperClassType(filterReference)
+
+
+        if (superClass != null && !superClass.isJavaLangObject()) {
+            val qualifiedName = superClass.toTypeString()
+            writer.print(" extends ")
+
+            if (qualifiedName.contains("<")) {
+                // TODO: I need to push this into the model at filter-time such that clients don't need
+                // to remember to do this!!
+                val s = superClass.asClass()
+                if (s != null) {
+                    val map = cls.mapTypeVariables(s)
+                    val replaced = superClass.convertTypeString(map)
+                    writer.print(replaced)
+                    return
+                }
+            }
+            (cls as PsiClassItem).psiClass.superClassType
+            writer.print(qualifiedName)
+        }
+    }
+
+    private fun generateInterfaceList(cls: ClassItem) {
+        if (cls.isAnnotationType()) {
+            // No extends statement for annotations; it's implied by the "@interface" keyword
+            return
+        }
+
+        val interfaces = if (preFiltered)
+            cls.interfaceTypes().asSequence()
+        else
+            cls.filteredInterfaceTypes(filterReference).asSequence()
+
+        if (interfaces.any()) {
+            if (cls.isInterface() && cls.superClassType() != null)
+                writer.print(", ")
+            else
+                writer.print(" implements")
+            interfaces.forEachIndexed { index, type ->
+                if (index > 0) {
+                    writer.print(",")
+                }
+                writer.print(" ")
+                writer.print(type.toTypeString())
+            }
+        } else if (compatibility.classForAnnotations && cls.isAnnotationType()) {
+            writer.print(" implements java.lang.annotation.Annotation")
+        }
+    }
+
+    private fun generateTypeParameterList(
+        typeList: String?,
+        addSpace: Boolean
+    ) {
+        // TODO: Do I need to map type variables?
+
+        if (typeList != null) {
+            writer.print(typeList)
+
+            if (addSpace) {
+                writer.print(' ')
+            }
+        }
+    }
+
+    override fun visitConstructor(constructor: ConstructorItem) {
+        writeConstructor(constructor, constructor.superConstructor)
+    }
+
+    private fun writeConstructor(
+        constructor: MethodItem,
+        superConstructor: MethodItem?
+    ) {
+        writer.println()
+        appendDocumentation(constructor, writer)
+        appendModifiers(constructor, false)
+        generateTypeParameterList(
+            typeList = constructor.typeParameterList(),
+            addSpace = true
+        )
+        writer.print(constructor.containingClass().simpleName())
+
+        generateParameterList(constructor)
+        generateThrowsList(constructor)
+
+        writer.print(" { ")
+
+        writeConstructorBody(constructor, superConstructor)
+        writer.println(" }")
+    }
+
+    private fun writeConstructorBody(constructor: MethodItem?, superConstructor: MethodItem?) {
+        // Find any constructor in parent that we can compile against
+        superConstructor?.let { it ->
+            val parameters = it.parameters()
+            val invokeOnThis = constructor != null && constructor.containingClass() == it.containingClass()
+            if (invokeOnThis || parameters.isNotEmpty()) {
+                val includeCasts = parameters.isNotEmpty() &&
+                        it.containingClass().constructors().filter { filterReference.test(it) }.size > 1
+                if (invokeOnThis) {
+                    writer.print("this(")
+                } else {
+                    writer.print("super(")
+                }
+                parameters.forEachIndexed { index, parameter ->
+                    if (index > 0) {
+                        writer.write(", ")
+                    }
+                    val type = parameter.type()
+                    val typeString = type.toErasedTypeString()
+                    if (!type.primitive) {
+                        if (includeCasts) {
+                            writer.write("(")
+
+                            // Types with varargs can't appear as varargs when used as an argument
+                            if (typeString.contains("...")) {
+                                writer.write(typeString.replace("...", "[]"))
+                            } else {
+                                writer.write(typeString)
+                            }
+                            writer.write(")")
+                        }
+                        writer.write("null")
+
+                    } else {
+                        if (typeString != "boolean" && typeString != "int" && typeString != "long") {
+                            writer.write("(")
+                            writer.write(typeString)
+                            writer.write(")")
+                        }
+                        writer.write(type.defaultValueString())
+                    }
+                }
+                writer.print("); ")
+            }
+        }
+
+        writeThrowStub()
+    }
+
+    private fun generateMissingConstructors(cls: ClassItem) {
+        val clsDefaultConstructor = cls.defaultConstructor
+        val constructors = cls.filteredConstructors(filterEmit)
+        if (clsDefaultConstructor != null && !constructors.contains(clsDefaultConstructor)) {
+            clsDefaultConstructor.mutableModifiers().setPackagePrivate(true)
+            visitConstructor(clsDefaultConstructor)
+            return
+        }
+    }
+
+    override fun visitMethod(method: MethodItem) {
+        writeMethod(method.containingClass(), method, false)
+    }
+
+    private fun writeMethod(containingClass: ClassItem, method: MethodItem, movedFromInterface: Boolean) {
+        val modifiers = method.modifiers
+        val isEnum = containingClass.isEnum()
+        val isAnnotation = containingClass.isAnnotationType()
+
+        if (isEnum && (method.name() == "values" ||
+                    method.name() == "valueOf" && method.parameters().size == 1 &&
+                    method.parameters()[0].type().toTypeString() == "java.lang.String")
+        ) {
+            // Skip the values() and valueOf(String) methods in enums: these are added by
+            // the compiler for enums anyway, but was part of the doclava1 signature files
+            // so inserted in compat mode.
+            return
+        }
+
+        writer.println()
+        appendDocumentation(method, writer)
+
+        // Need to filter out abstract from the modifiers list and turn it
+        // into a concrete method to make the stub compile
+        val removeAbstract = modifiers.isAbstract() && (isEnum || isAnnotation) || movedFromInterface
+
+        appendModifiers(method, modifiers, removeAbstract, movedFromInterface)
+        generateTypeParameterList(typeList = method.typeParameterList(), addSpace = true)
+
+        val returnType = method.returnType()
+        writer.print(returnType?.toTypeString(outerAnnotations = false, innerAnnotations = true))
+
+        writer.print(' ')
+        writer.print(method.name())
+        generateParameterList(method)
+        generateThrowsList(method)
+
+        if (modifiers.isAbstract() && !removeAbstract && !isEnum || isAnnotation || modifiers.isNative()) {
+            writer.println(";")
+        } else {
+            writer.print(" { ")
+            writeThrowStub()
+            writer.println(" }")
+        }
+    }
+
+    override fun visitField(field: FieldItem) {
+        // Handled earlier in visitClass
+        if (field.isEnumConstant()) {
+            return
+        }
+
+        writer.println()
+
+        appendDocumentation(field, writer)
+        appendModifiers(field, false, false)
+        writer.print(field.type().toTypeString(outerAnnotations = false, innerAnnotations = true))
+        writer.print(' ')
+        writer.print(field.name())
+        val needsInitialization =
+            field.modifiers.isFinal() && field.initialValue(true) == null && field.containingClass().isClass()
+        field.writeValueWithSemicolon(
+            writer,
+            allowDefaultValue = !needsInitialization,
+            requireInitialValue = !needsInitialization
+        )
+        writer.print("\n")
+
+        if (needsInitialization) {
+            if (field.modifiers.isStatic()) {
+                writer.print("static ")
+            }
+            writer.print("{ ${field.name()} = ${field.type().defaultValueString()}; }\n")
+        }
+    }
+
+    private fun writeThrowStub() {
+        writer.write("throw new RuntimeException(\"Stub!\");")
+    }
+
+    private fun generateParameterList(method: MethodItem) {
+        writer.print("(")
+        method.parameters().asSequence().forEachIndexed { i, parameter ->
+            if (i > 0) {
+                writer.print(", ")
+            }
+            appendModifiers(parameter, false)
+            writer.print(parameter.type().toTypeString(outerAnnotations = false, innerAnnotations = true))
+            writer.print(' ')
+            val name = parameter.publicName() ?: parameter.name()
+            writer.print(name)
+        }
+        writer.print(")")
+    }
+
+    private fun generateThrowsList(method: MethodItem) {
+        // Note that throws types are already sorted internally to help comparison matching
+        val throws = if (preFiltered)
+            method.throwsTypes().asSequence()
+        else
+            method.throwsTypes().asSequence().filter { filterReference.test(it) }
+        if (throws.any()) {
+            writer.print(" throws ")
+            throws.asSequence().sortedWith(ClassItem.fullNameComparator).forEachIndexed { i, type ->
+                if (i > 0) {
+                    writer.print(", ")
+                }
+                // TODO: Shouldn't declare raw types here!
+                writer.print(type.qualifiedName())
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/Terminal.kt b/src/main/java/com/android/tools/metalava/Terminal.kt
new file mode 100644
index 0000000..8a32691
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/Terminal.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava
+
+import java.io.PrintWriter
+
+enum class TerminalColor(val value: Int) {
+    BLACK(0),
+    RED(1),
+    GREEN(2),
+    YELLOW(3),
+    BLUE(4),
+    MAGENTA(5),
+    CYAN(6),
+    WHITE(7)
+}
+
+fun terminalAttributes(
+    bold: Boolean = false, underline: Boolean = false, reverse: Boolean = false,
+    foreground: TerminalColor? = null, background: TerminalColor? = null
+): String {
+    val sb = StringBuilder()
+    sb.append("\u001B[")
+    if (foreground != null) {
+        sb.append('3').append('0' + foreground.value)
+    }
+    if (background != null) {
+        if (sb.last().isDigit())
+            sb.append(';')
+        sb.append('4').append('0' + background.value)
+    }
+
+    if (bold) {
+        if (sb.last().isDigit())
+            sb.append(';')
+        sb.append('1')
+    }
+    if (underline) {
+        if (sb.last().isDigit())
+            sb.append(';')
+        sb.append('4')
+    }
+    if (reverse) {
+        if (sb.last().isDigit())
+            sb.append(';')
+        sb.append('7')
+    }
+    if (sb.last() == '[') {
+        // Nothing: Reset
+        sb.append('0')
+    }
+    sb.append("m")
+    return sb.toString()
+}
+
+fun resetTerminal(): String {
+    return "\u001b[0m"
+}
+
+fun colorized(string: String, color: TerminalColor): String {
+    return "${terminalAttributes(foreground = color)}$string${resetTerminal()}"
+}
+
+fun bold(string: String): String {
+    return "${terminalAttributes(bold = true)}$string${resetTerminal()}"
+}
+
+fun PrintWriter.terminalPrint(
+    string: String,
+    bold: Boolean = false, underline: Boolean = false, reverse: Boolean = false,
+    foreground: TerminalColor? = null, background: TerminalColor? = null
+) {
+    print(
+        terminalAttributes(
+            bold = bold, underline = underline, reverse = reverse, foreground = foreground,
+            background = background
+        )
+    )
+    print(string)
+    print(resetTerminal())
+}
diff --git a/src/main/java/com/android/tools/metalava/apilevels/AndroidJarReader.java b/src/main/java/com/android/tools/metalava/apilevels/AndroidJarReader.java
new file mode 100644
index 0000000..378d536
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/apilevels/AndroidJarReader.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+package com.android.tools.metalava.apilevels;
+
+import com.android.annotations.NonNull;
+import com.google.common.io.ByteStreams;
+import com.google.common.io.Closeables;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.tree.ClassNode;
+import org.objectweb.asm.tree.FieldNode;
+import org.objectweb.asm.tree.MethodNode;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.List;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+/**
+ * Reads all the android.jar files found in an SDK and generate a map of {@link ApiClass}.
+ */
+public class AndroidJarReader {
+    private int mMinApi;
+    private int mCurrentApi;
+    private File mCurrentJar;
+    private List<String> mPatterns;
+    private File[] mApiLevels;
+
+    AndroidJarReader(@NonNull List<String> patterns, int minApi, @NonNull File currentJar, int currentApi) {
+        mPatterns = patterns;
+        mMinApi = minApi;
+        mCurrentJar = currentJar;
+        mCurrentApi = currentApi;
+    }
+
+    AndroidJarReader(@NonNull File[] apiLevels) {
+        mApiLevels = apiLevels;
+    }
+
+    public Api getApi() throws IOException {
+        Api api = new Api();
+        if (mApiLevels != null) {
+            for (int apiLevel = 1; apiLevel < mApiLevels.length; apiLevel++) {
+                File jar = getAndroidJarFile(apiLevel);
+                readJar(api, apiLevel, jar);
+            }
+        } else {
+            // Get all the android.jar. They are in platforms-#
+            int apiLevel = mMinApi - 1;
+            while (true) {
+                apiLevel++;
+                File jar = null;
+                if (apiLevel == mCurrentApi) {
+                    jar = mCurrentJar;
+                }
+                if (jar == null) {
+                    jar = getAndroidJarFile(apiLevel);
+                }
+                if (jar == null || !jar.isFile()) {
+                    System.out.println("Last API level found: " + (apiLevel - 1));
+                    break;
+                }
+                System.out.println("Found API " + apiLevel + " at " + jar.getPath());
+                readJar(api, apiLevel, jar);
+            }
+        }
+
+        api.removeImplicitInterfaces();
+        api.removeOverridingMethods();
+
+        return api;
+    }
+
+    private void readJar(Api api, int apiLevel, File jar) throws IOException {
+        api.update(apiLevel);
+
+        FileInputStream fis = new FileInputStream(jar);
+        ZipInputStream zis = new ZipInputStream(fis);
+        ZipEntry entry = zis.getNextEntry();
+        while (entry != null) {
+            String name = entry.getName();
+
+            if (name.endsWith(".class")) {
+                byte[] bytes = ByteStreams.toByteArray(zis);
+                if (bytes == null) {
+                    System.err.println("Warning: Couldn't read " + name);
+                    entry = zis.getNextEntry();
+                    continue;
+                }
+
+                ClassReader reader = new ClassReader(bytes);
+                ClassNode classNode = new ClassNode(Opcodes.ASM5);
+                reader.accept(classNode, 0 /*flags*/);
+
+                ApiClass theClass = api.addClass(classNode.name, apiLevel,
+                        (classNode.access & Opcodes.ACC_DEPRECATED) != 0);
+
+                // super class
+                if (classNode.superName != null) {
+                    theClass.addSuperClass(classNode.superName, apiLevel);
+                }
+
+                // interfaces
+                for (Object interfaceName : classNode.interfaces) {
+                    theClass.addInterface((String) interfaceName, apiLevel);
+                }
+
+                // fields
+                for (Object field : classNode.fields) {
+                    FieldNode fieldNode = (FieldNode) field;
+                    if ((fieldNode.access & Opcodes.ACC_PRIVATE) != 0) {
+                        continue;
+                    }
+                    if (!fieldNode.name.startsWith("this$") &&
+                            !fieldNode.name.equals("$VALUES")) {
+                        boolean deprecated = (fieldNode.access & Opcodes.ACC_DEPRECATED) != 0;
+                        theClass.addField(fieldNode.name, apiLevel, deprecated);
+                    }
+                }
+
+                // methods
+                for (Object method : classNode.methods) {
+                    MethodNode methodNode = (MethodNode) method;
+                    if ((methodNode.access & Opcodes.ACC_PRIVATE) != 0) {
+                        continue;
+                    }
+                    if (!methodNode.name.equals("<clinit>")) {
+                        boolean deprecated = (methodNode.access & Opcodes.ACC_DEPRECATED) != 0;
+                        theClass.addMethod(methodNode.name + methodNode.desc, apiLevel, deprecated);
+                    }
+                }
+            }
+            entry = zis.getNextEntry();
+        }
+
+        Closeables.close(fis, true);
+    }
+
+    private File getAndroidJarFile(int apiLevel) {
+        if (mApiLevels != null) {
+            return mApiLevels[apiLevel];
+        }
+        for (String pattern : mPatterns) {
+            File f = new File(pattern.replace("%", Integer.toString(apiLevel)));
+            if (f.isFile()) {
+                return f;
+            }
+        }
+        return null;
+    }
+}
diff --git a/src/main/java/com/android/tools/metalava/apilevels/Api.java b/src/main/java/com/android/tools/metalava/apilevels/Api.java
new file mode 100644
index 0000000..30dc5f7
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/apilevels/Api.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+package com.android.tools.metalava.apilevels;
+
+import java.io.PrintStream;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Represents the whole Android API.
+ */
+public class Api extends ApiElement {
+    private final Map<String, ApiClass> mClasses = new HashMap<String, ApiClass>();
+
+    public Api() {
+        // Pretend that API started from version 0 to make sure that classes existed in the first version
+        // are printed with since="1".
+        super("Android API");
+    }
+
+    /**
+     * Prints the whole API definition to a stream.
+     *
+     * @param stream the stream to print the XML elements to
+     */
+    public void print(PrintStream stream) {
+        stream.println("<api version=\"2\">");
+        print(mClasses.values(), "class", "\t", stream);
+        printClosingTag("api", "", stream);
+    }
+
+    /**
+     * Adds or updates a class.
+     *
+     * @param name       the name of the class
+     * @param version    an API version in which the class existed
+     * @param deprecated whether the class was deprecated in the API version
+     * @return the newly created or a previously existed class
+     */
+    public ApiClass addClass(String name, int version, boolean deprecated) {
+        ApiClass classElement = mClasses.get(name);
+        if (classElement == null) {
+            classElement = new ApiClass(name, version, deprecated);
+            mClasses.put(name, classElement);
+        } else {
+            classElement.update(version, deprecated);
+        }
+        return classElement;
+    }
+
+    /**
+     * The bytecode visitor registers interfaces listed for a class. However,
+     * a class will <b>also</b> implement interfaces implemented by the super classes.
+     * This isn't available in the class file, so after all classes have been read in,
+     * we iterate through all classes, and for those that have interfaces, we check up
+     * the inheritance chain to see if it has already been introduced in a super class
+     * at an earlier API level.
+     */
+    public void removeImplicitInterfaces() {
+        for (ApiClass classElement : mClasses.values()) {
+            classElement.removeImplicitInterfaces(mClasses);
+        }
+    }
+
+    /**
+     * @see ApiClass#removeOverridingMethods
+     */
+    public void removeOverridingMethods() {
+        for (ApiClass classElement : mClasses.values()) {
+            classElement.removeOverridingMethods(mClasses);
+        }
+    }
+}
diff --git a/src/main/java/com/android/tools/metalava/apilevels/ApiClass.java b/src/main/java/com/android/tools/metalava/apilevels/ApiClass.java
new file mode 100644
index 0000000..a9bcc19
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/apilevels/ApiClass.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+package com.android.tools.metalava.apilevels;
+
+import com.google.common.collect.Iterables;
+
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Represents a class or an interface and its methods/fields.
+ * This is used to write the simplified XML file containing all the public API.
+ */
+public class ApiClass extends ApiElement {
+    private final List<ApiElement> mSuperClasses = new ArrayList<ApiElement>();
+    private final List<ApiElement> mInterfaces = new ArrayList<ApiElement>();
+
+    private final Map<String, ApiElement> mFields = new HashMap<String, ApiElement>();
+    private final Map<String, ApiElement> mMethods = new HashMap<String, ApiElement>();
+
+    public ApiClass(String name, int version, boolean deprecated) {
+        super(name, version, deprecated);
+    }
+
+    public void addField(String name, int version, boolean deprecated) {
+        addToMap(mFields, name, version, deprecated);
+    }
+
+    public void addMethod(String name, int version, boolean deprecated) {
+        addToMap(mMethods, name, version, deprecated);
+    }
+
+    public void addSuperClass(String superClass, int since) {
+        addToArray(mSuperClasses, superClass, since);
+    }
+
+    public void addInterface(String interfaceClass, int since) {
+        addToArray(mInterfaces, interfaceClass, since);
+    }
+
+    private void addToMap(Map<String, ApiElement> elements, String name, int version, boolean deprecated) {
+        ApiElement element = elements.get(name);
+        if (element == null) {
+            element = new ApiElement(name, version, deprecated);
+            elements.put(name, element);
+        } else {
+            element.update(version, deprecated);
+        }
+    }
+
+    private void addToArray(Collection<ApiElement> elements, String name, int version) {
+        ApiElement element = findByName(elements, name);
+        if (element == null) {
+            element = new ApiElement(name, version);
+            elements.add(element);
+        } else {
+            element.update(version);
+        }
+    }
+
+    private ApiElement findByName(Collection<ApiElement> collection, String name) {
+        for (ApiElement element : collection) {
+            if (element.getName().equals(name)) {
+                return element;
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public void print(String tag, ApiElement parentElement, String indent, PrintStream stream) {
+        super.print(tag, false, parentElement, indent, stream);
+        String innerIndent = indent + '\t';
+        print(mSuperClasses, "extends", innerIndent, stream);
+        print(mInterfaces, "implements", innerIndent, stream);
+        print(mMethods.values(), "method", innerIndent, stream);
+        print(mFields.values(), "field", innerIndent, stream);
+        printClosingTag(tag, indent, stream);
+    }
+
+    /**
+     * Removes all interfaces that are also implemented by superclasses or extended by interfaces
+     * this class implements.
+     *
+     * @param allClasses all classes keyed by their names.
+     */
+    public void removeImplicitInterfaces(Map<String, ApiClass> allClasses) {
+        if (mInterfaces.isEmpty() || mSuperClasses.isEmpty()) {
+            return;
+        }
+
+        for (Iterator<ApiElement> iterator = mInterfaces.iterator(); iterator.hasNext(); ) {
+            ApiElement interfaceElement = iterator.next();
+
+            for (ApiElement superClass : mSuperClasses) {
+                if (superClass.introducedNotLaterThan(interfaceElement)) {
+                    ApiClass cls = allClasses.get(superClass.getName());
+                    if (cls != null && cls.implementsInterface(interfaceElement, allClasses)) {
+                        iterator.remove();
+                        break;
+                    }
+                }
+            }
+        }
+    }
+
+    private boolean implementsInterface(ApiElement interfaceElement, Map<String, ApiClass> allClasses) {
+        for (ApiElement localInterface : mInterfaces) {
+            if (localInterface.introducedNotLaterThan(interfaceElement)) {
+                if (interfaceElement.getName().equals(localInterface.getName())) {
+                    return true;
+                }
+                // Check parent interface.
+                ApiClass cls = allClasses.get(localInterface.getName());
+                if (cls != null && cls.implementsInterface(interfaceElement, allClasses)) {
+                    return true;
+                }
+            }
+        }
+
+        for (ApiElement superClass : mSuperClasses) {
+            if (superClass.introducedNotLaterThan(interfaceElement)) {
+                ApiClass cls = allClasses.get(superClass.getName());
+                if (cls != null && cls.implementsInterface(interfaceElement, allClasses)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Removes all methods that override method declared by superclasses and interfaces of this class.
+     *
+     * @param allClasses all classes keyed by their names.
+     */
+    public void removeOverridingMethods(Map<String, ApiClass> allClasses) {
+        for (Iterator<Map.Entry<String, ApiElement>> iter = mMethods.entrySet().iterator(); iter.hasNext(); ) {
+            Map.Entry<String, ApiElement> entry = iter.next();
+            ApiElement method = entry.getValue();
+            if (!method.getName().startsWith("<init>(") && isOverrideOfInherited(method, allClasses)) {
+                iter.remove();
+            }
+        }
+    }
+
+    /**
+     * Checks if the given method overrides one of the methods defined by this class or
+     * its superclasses or interfaces.
+     *
+     * @param method     the method to check
+     * @param allClasses the map containing all API classes
+     * @return true if the method is an override
+     */
+    private boolean isOverride(ApiElement method, Map<String, ApiClass> allClasses) {
+        ApiElement localMethod = mMethods.get(method.getName());
+        if (localMethod != null && localMethod.introducedNotLaterThan(method)) {
+            // This class has the method and it was introduced in at the same api level
+            // as the child method, or before.
+            return true;
+        }
+        return isOverrideOfInherited(method, allClasses);
+    }
+
+    /**
+     * Checks if the given method overrides one of the methods declared by ancestors of this class.
+     */
+    private boolean isOverrideOfInherited(ApiElement method, Map<String, ApiClass> allClasses) {
+        // Check this class' parents.
+        for (ApiElement parent : Iterables.concat(mSuperClasses, mInterfaces)) {
+            // Only check the parent if it was a parent class at the introduction of the method.
+            if (parent.introducedNotLaterThan(method)) {
+                ApiClass cls = allClasses.get(parent.getName());
+                if (cls != null && cls.isOverride(method, allClasses)) {
+                    return true;
+                }
+            }
+        }
+
+        return false;
+    }
+
+    @Override
+    public String toString() {
+        return getName();
+    }
+}
diff --git a/src/main/java/com/android/tools/metalava/apilevels/ApiElement.java b/src/main/java/com/android/tools/metalava/apilevels/ApiElement.java
new file mode 100644
index 0000000..ead821c
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/apilevels/ApiElement.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+package com.android.tools.metalava.apilevels;
+
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Represents an API element, e.g. class, method or field.
+ */
+public class ApiElement implements Comparable<ApiElement> {
+    private final String mName;
+    private int mSince;
+    private int mDeprecatedIn;
+    private int mLastPresentIn;
+
+    /**
+     * @param name       the name of the API element
+     * @param version    an API version for which the API element existed
+     * @param deprecated whether the API element was deprecated in the API version in question
+     */
+    public ApiElement(String name, int version, boolean deprecated) {
+        assert name != null;
+        assert version > 0;
+        mName = name;
+        mSince = version;
+        mLastPresentIn = version;
+        if (deprecated) {
+            mDeprecatedIn = version;
+        }
+    }
+
+    /**
+     * @param name    the name of the API element
+     * @param version an API version for which the API element existed
+     */
+    public ApiElement(String name, int version) {
+        this(name, version, false);
+    }
+
+    protected ApiElement(String name) {
+        assert name != null;
+        mName = name;
+    }
+
+    /**
+     * Returns the name of the API element.
+     */
+    public final String getName() {
+        return mName;
+    }
+
+    /**
+     * Checks if this API element was introduced not later than another API element.
+     *
+     * @param other the API element to compare to
+     * @return true if this API element was introduced not later than {@code other}
+     */
+    public final boolean introducedNotLaterThan(ApiElement other) {
+        return mSince <= other.mSince;
+    }
+
+    /**
+     * Updates the API element with information for a specific API version.
+     *
+     * @param version    an API version for which the API element existed
+     * @param deprecated whether the API element was deprecated in the API version in question
+     */
+    public void update(int version, boolean deprecated) {
+        assert version > 0;
+        if (mSince > version) {
+            mSince = version;
+        }
+        if (mLastPresentIn < version) {
+            mLastPresentIn = version;
+        }
+        if (deprecated) {
+            if (mDeprecatedIn == 0 || mDeprecatedIn > version) {
+                mDeprecatedIn = version;
+            }
+        }
+    }
+
+    /**
+     * Updates the API element with information for a specific API version.
+     *
+     * @param version an API version for which the API element existed
+     */
+    public void update(int version) {
+        update(version, false);
+    }
+
+    /**
+     * Checks whether the API element is deprecated or not.
+     */
+    public final boolean isDeprecated() {
+        return mDeprecatedIn != 0;
+    }
+
+    /**
+     * Prints an XML representation of the element to a stream terminated by a line break.
+     * Attributes with values matching the parent API element are omitted.
+     *
+     * @param tag           the tag of the XML element
+     * @param parentElement the parent API element
+     * @param indent        the whitespace prefix to insert before the XML element
+     * @param stream        the stream to print the XML element to
+     */
+    public void print(String tag, ApiElement parentElement, String indent, PrintStream stream) {
+        print(tag, true, parentElement, indent, stream);
+    }
+
+    /**
+     * Prints an XML representation of the element to a stream terminated by a line break.
+     * Attributes with values matching the parent API element are omitted.
+     *
+     * @param tag           the tag of the XML element
+     * @param closeTag      if true the XML element is terminated by "/>", otherwise the closing
+     *                      tag of the element is not printed
+     * @param parentElement the parent API element
+     * @param indent        the whitespace prefix to insert before the XML element
+     * @param stream        the stream to print the XML element to
+     * @see #printClosingTag(String, String, PrintStream)
+     */
+    protected void print(String tag, boolean closeTag, ApiElement parentElement, String indent,
+                         PrintStream stream) {
+        stream.print(indent);
+        stream.print('<');
+        stream.print(tag);
+        stream.print(" name=\"");
+        stream.print(encodeAttribute(mName));
+        if (mSince > parentElement.mSince) {
+            stream.print("\" since=\"");
+            stream.print(mSince);
+        }
+        if (mDeprecatedIn != 0) {
+            stream.print("\" deprecated=\"");
+            stream.print(mDeprecatedIn);
+        }
+        if (mLastPresentIn < parentElement.mLastPresentIn) {
+            stream.print("\" removed=\"");
+            stream.print(mLastPresentIn + 1);
+        }
+        stream.print('"');
+        if (closeTag) {
+            stream.print('/');
+        }
+        stream.println('>');
+    }
+
+    /**
+     * Prints homogeneous XML elements to a stream. Each element is printed on a separate line.
+     * Attributes with values matching the parent API element are omitted.
+     *
+     * @param elements the elements to print
+     * @param tag      the tag of the XML elements
+     * @param indent   the whitespace prefix to insert before each XML element
+     * @param stream   the stream to print the XML elements to
+     */
+    protected void print(Collection<? extends ApiElement> elements, String tag, String indent, PrintStream stream) {
+        for (ApiElement element : sortedList(elements)) {
+            element.print(tag, this, indent, stream);
+        }
+    }
+
+    private <T extends ApiElement> List<T> sortedList(Collection<T> elements) {
+        List<T> list = new ArrayList<T>(elements);
+        Collections.sort(list);
+        return list;
+    }
+
+    /**
+     * Prints a closing tag of an XML element terminated by a line break.
+     *
+     * @param tag    the tag of the element
+     * @param indent the whitespace prefix to insert before the closing tag
+     * @param stream the stream to print the XML element to
+     */
+    protected static void printClosingTag(String tag, String indent, PrintStream stream) {
+        stream.print(indent);
+        stream.print("</");
+        stream.print(tag);
+        stream.println('>');
+    }
+
+    protected static String encodeAttribute(String attribute) {
+        StringBuilder sb = new StringBuilder();
+        int n = attribute.length();
+        // &, ", ' and < are illegal in attributes; see http://www.w3.org/TR/REC-xml/#NT-AttValue
+        // (' legal in a " string and " is legal in a ' string but here we'll stay on the safe side).
+        for (int i = 0; i < n; i++) {
+            char c = attribute.charAt(i);
+            if (c == '"') {
+                sb.append("&quot;"); //$NON-NLS-1$
+            } else if (c == '<') {
+                sb.append("&lt;"); //$NON-NLS-1$
+            } else if (c == '\'') {
+                sb.append("&apos;"); //$NON-NLS-1$
+            } else if (c == '&') {
+                sb.append("&amp;"); //$NON-NLS-1$
+            } else {
+                sb.append(c);
+            }
+        }
+
+        return sb.toString();
+    }
+
+    @Override
+    public int compareTo(ApiElement other) {
+        return mName.compareTo(other.mName);
+    }
+}
diff --git a/src/main/java/com/android/tools/metalava/apilevels/ApiGenerator.java b/src/main/java/com/android/tools/metalava/apilevels/ApiGenerator.java
new file mode 100644
index 0000000..e3b3225
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/apilevels/ApiGenerator.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava.apilevels;
+
+import com.android.annotations.NonNull;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Main class for command line command to convert the existing API XML/TXT files into diff-based
+ * simple text files.
+ */
+public class ApiGenerator {
+    public static void main(String[] args) {
+        boolean error = false;
+        int minApi = 1;
+        int currentApi = -1;
+        String currentCodename = null;
+        File currentJar = null;
+        List<String> patterns = new ArrayList<>();
+        String outPath = null;
+
+        for (int i = 0; i < args.length && !error; i++) {
+            String arg = args[i];
+
+            if (arg.equals("--pattern")) {
+                i++;
+                if (i < args.length) {
+                    patterns.add(args[i]);
+                } else {
+                    System.err.println("Missing argument after " + arg);
+                    error = true;
+                }
+            } else if (arg.equals("--current-version")) {
+                i++;
+                if (i < args.length) {
+                    currentApi = Integer.parseInt(args[i]);
+                    if (currentApi <= 22) {
+                        System.err.println("Suspicious currentApi=" + currentApi + ", expected at least 23");
+                        error = true;
+                    }
+                } else {
+                    System.err.println("Missing number >= 1 after " + arg);
+                    error = true;
+                }
+            } else if (arg.equals("--current-codename")) {
+                i++;
+                if (i < args.length) {
+                    currentCodename = args[i];
+                } else {
+                    System.err.println("Missing codename after " + arg);
+                    error = true;
+                }
+            } else if (arg.equals("--current-jar")) {
+                i++;
+                if (i < args.length) {
+                    if (currentJar != null) {
+                        System.err.println("--current-jar should only be specified once");
+                        error = true;
+                    }
+                    String path = args[i];
+                    currentJar = new File(path);
+                } else {
+                    System.err.println("Missing argument after " + arg);
+                    error = true;
+                }
+            } else if (arg.equals("--min-api")) {
+                i++;
+                if (i < args.length) {
+                    minApi = Integer.parseInt(args[i]);
+                } else {
+                    System.err.println("Missing number >= 1 after " + arg);
+                    error = true;
+                }
+            } else if (arg.length() >= 2 && arg.substring(0, 2).equals("--")) {
+                System.err.println("Unknown argument: " + arg);
+                error = true;
+
+            } else if (outPath == null) {
+                outPath = arg;
+
+            } else if (new File(arg).isDirectory()) {
+                String pattern = arg;
+                if (!pattern.endsWith(File.separator)) {
+                    pattern += File.separator;
+                }
+                pattern += "platforms" + File.separator + "android-%" + File.separator + "android.jar";
+                patterns.add(pattern);
+
+            } else {
+                System.err.println("Unknown argument: " + arg);
+                error = true;
+            }
+        }
+
+        if (!error && outPath == null) {
+            System.err.println("Missing out file path");
+            error = true;
+        }
+
+        if (!error && patterns.isEmpty()) {
+            System.err.println("Missing SdkFolder or --pattern.");
+            error = true;
+        }
+
+        if (currentJar != null && currentApi == -1 || currentJar == null && currentApi != -1) {
+            System.err.println("You must specify both --current-jar and --current-version (or neither one)");
+            error = true;
+        }
+
+        // The SDK version number
+        if (currentCodename != null && !"REL".equals(currentCodename)) {
+            currentApi++;
+        }
+
+        if (error) {
+            printUsage();
+            System.exit(1);
+        }
+
+        try {
+            if (!generate(minApi, currentApi, currentJar, patterns, outPath)) {
+                System.exit(1);
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+            System.exit(-1);
+        }
+    }
+
+    private static boolean generate(int minApi,
+                                    int currentApi,
+                                    @NonNull File currentJar,
+                                    @NonNull List<String> patterns,
+                                    @NonNull String outPath) throws IOException {
+        AndroidJarReader reader = new AndroidJarReader(patterns, minApi, currentJar, currentApi);
+        Api api = reader.getApi();
+        return createApiFile(new File(outPath), api);
+    }
+
+    public static boolean generate(@NonNull File[] apiLevels, @NonNull File outputFile) throws IOException {
+        AndroidJarReader reader = new AndroidJarReader(apiLevels);
+        Api api = reader.getApi();
+        return createApiFile(outputFile, api);
+    }
+
+    private static void printUsage() {
+        System.err.println("\nGenerates a single API file from the content of an SDK.");
+        System.err.println("Usage:");
+        System.err.println("\tApiCheck [--min-api=1] OutFile [SdkFolder | --pattern sdk/%/android.jar]+");
+        System.err.println("Options:");
+        System.err.println("--min-api <int> : The first API level to consider (>=1).");
+        System.err.println("--pattern <pattern>: Path pattern to find per-API android.jar files, where\n" +
+                "            '%' is replaced by the API level.");
+        System.err.println("--current-jar <path>: Path pattern to find the current android.jar");
+        System.err.println("--current-version <int>: The API level for the current API");
+        System.err.println("--current-codename <name>: REL, if a release, or codename for previews");
+        System.err.println("SdkFolder: if given, this adds the pattern\n" +
+                "           '$SdkFolder/platforms/android-%/android.jar'");
+        System.err.println("If multiple --pattern are specified, they are tried in the order given.\n");
+    }
+
+    /**
+     * Creates the simplified diff-based API level.
+     *
+     * @param outFile the output file
+     * @param api     the api to write
+     */
+    private static boolean createApiFile(File outFile, Api api) {
+        PrintStream stream = null;
+        try {
+            File parentFile = outFile.getParentFile();
+            if (!parentFile.exists()) {
+                boolean ok = parentFile.mkdirs();
+                if (!ok) {
+                    System.err.println("Could not create directory " + parentFile);
+                    return false;
+                }
+            }
+            stream = new PrintStream(outFile, "UTF-8");
+            stream.println("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
+            api.print(stream);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        } finally {
+            if (stream != null) {
+                stream.close();
+            }
+        }
+
+        return true;
+    }
+}
diff --git a/src/main/java/com/android/tools/metalava/doclava1/ApiFile.java b/src/main/java/com/android/tools/metalava/doclava1/ApiFile.java
new file mode 100644
index 0000000..dbec782
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/doclava1/ApiFile.java
@@ -0,0 +1,877 @@
+/*
+ * Copyright (C) 2011 Google 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.
+ */
+
+package com.android.tools.metalava.doclava1;
+
+import com.android.tools.lint.annotations.Extractor;
+import com.android.tools.lint.checks.infrastructure.ClassNameKt;
+import com.android.tools.metalava.model.AnnotationItem;
+import com.android.tools.metalava.model.text.TextClassItem;
+import com.android.tools.metalava.model.text.TextConstructorItem;
+import com.android.tools.metalava.model.text.TextFieldItem;
+import com.android.tools.metalava.model.text.TextMethodItem;
+import com.android.tools.metalava.model.text.TextPackageItem;
+import com.android.tools.metalava.model.text.TextParameterItem;
+import com.android.tools.metalava.model.text.TextTypeItem;
+import com.google.common.base.Charsets;
+import com.google.common.io.Files;
+import kotlin.Pair;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import static com.android.tools.metalava.model.FieldItemKt.javaUnescapeString;
+
+//
+// Copied from doclava1, but adapted to metalava's code model (plus tweaks to handle
+// metalava's richer files, e.g. annotations)
+//
+public class ApiFile {
+    public static ApiInfo parseApi(File file,
+                                   boolean kotlinStyleNulls,
+                                   boolean supportsStagedNullability) throws ApiParseException {
+        try {
+            String apiText = Files.asCharSource(file, Charsets.UTF_8).read();
+            return parseApi(file.getPath(), apiText, kotlinStyleNulls, supportsStagedNullability);
+        } catch (IOException ex) {
+            throw new ApiParseException("Error reading API file", ex);
+        }
+    }
+
+    public static ApiInfo parseApi(String filename, String apiText,
+                                   boolean kotlinStyleNulls,
+                                   boolean supportsStagedNullability) throws ApiParseException {
+        if (apiText.contains("/*")) {
+            apiText = ClassNameKt.stripComments(apiText, false); // line comments are used to stash field constants
+        }
+
+        final Tokenizer tokenizer = new Tokenizer(filename, apiText.toCharArray());
+        final ApiInfo api = new ApiInfo();
+        api.setSupportsStagedNullability(supportsStagedNullability);
+        api.setKotlinStyleNulls(kotlinStyleNulls);
+
+        while (true) {
+            String token = tokenizer.getToken();
+            if (token == null) {
+                break;
+            }
+            if ("package".equals(token)) {
+                parsePackage(api, tokenizer);
+            } else {
+                throw new ApiParseException("expected package got " + token, tokenizer.getLine());
+            }
+        }
+
+        api.postProcess();
+
+        return api;
+    }
+
+    private static void parsePackage(ApiInfo api, Tokenizer tokenizer)
+            throws ApiParseException {
+        String token;
+        String name;
+        TextPackageItem pkg;
+
+        token = tokenizer.requireToken();
+        assertIdent(tokenizer, token);
+        name = token;
+        pkg = new TextPackageItem(api, name, tokenizer.pos());
+        token = tokenizer.requireToken();
+        if (!"{".equals(token)) {
+            throw new ApiParseException("expected '{' got " + token, tokenizer.getLine());
+        }
+        while (true) {
+            token = tokenizer.requireToken();
+            if ("}".equals(token)) {
+                break;
+            } else {
+                parseClass(api, pkg, tokenizer, token);
+            }
+        }
+        api.addPackage(pkg);
+    }
+
+    private static void parseClass(ApiInfo api, TextPackageItem pkg, Tokenizer tokenizer, String token)
+            throws ApiParseException {
+        boolean pub = false;
+        boolean prot = false;
+        boolean priv = false;
+        boolean stat = false;
+        boolean fin = false;
+        boolean abs = false;
+        boolean dep = false;
+        boolean isInterface = false;
+        boolean isAnnotation = false;
+        boolean isEnum = false;
+        String name;
+        String qname;
+        String ext = null;
+        TextClassItem cl;
+
+        // Metalava: including annotations in file now
+        List<String> annotations = null;
+        Pair<String, List<String>> result = getAnnotations(tokenizer, token);
+        if (result != null) {
+            token = result.component1();
+            annotations = result.component2();
+        }
+
+        if ("public".equals(token)) {
+            pub = true;
+            token = tokenizer.requireToken();
+        } else if ("protected".equals(token)) {
+            prot = true;
+            token = tokenizer.requireToken();
+        } else if ("private".equals(token)) {
+            priv = true;
+            token = tokenizer.requireToken();
+        }
+        if ("static".equals(token)) {
+            stat = true;
+            token = tokenizer.requireToken();
+        }
+        if ("final".equals(token)) {
+            fin = true;
+            token = tokenizer.requireToken();
+        }
+        if ("abstract".equals(token)) {
+            abs = true;
+            token = tokenizer.requireToken();
+        }
+        if ("deprecated".equals(token)) {
+            dep = true;
+            token = tokenizer.requireToken();
+        }
+        if ("class".equals(token)) {
+            token = tokenizer.requireToken();
+        } else if ("interface".equals(token)) {
+            isInterface = true;
+            token = tokenizer.requireToken();
+        } else if ("@interface".equals(token)) {
+            // Annotation
+            abs = true;
+            isAnnotation = true;
+            token = tokenizer.requireToken();
+        } else if ("enum".equals(token)) {
+            isEnum = true;
+            fin = true;
+            ext = "java.lang.Enum";
+            token = tokenizer.requireToken();
+        } else {
+            throw new ApiParseException("missing class or interface. got: " + token, tokenizer.getLine());
+        }
+        assertIdent(tokenizer, token);
+        name = token;
+        qname = qualifiedName(pkg.name(), name);
+        final TextTypeItem typeInfo = api.obtainTypeFromString(qname);
+        // Simple type info excludes the package name (but includes enclosing class names)
+        final TextTypeItem simpleTypeInfo = api.obtainTypeFromString(name);
+        token = tokenizer.requireToken();
+
+        cl = new TextClassItem(api, tokenizer.pos(), pub, prot,
+                priv, stat, isInterface, abs, isEnum, isAnnotation,
+                fin, typeInfo.toErasedTypeString(), typeInfo.qualifiedTypeName(),
+                simpleTypeInfo.toErasedTypeString(), annotations);
+        cl.setContainingPackage(pkg);
+        cl.setTypeInfo(typeInfo);
+        cl.setDeprecated(dep);
+        if ("extends".equals(token)) {
+            token = tokenizer.requireToken();
+            assertIdent(tokenizer, token);
+            ext = token;
+            token = tokenizer.requireToken();
+        }
+        // Resolve superclass after done parsing
+        api.mapClassToSuper(cl, ext);
+        if ("implements".equals(token)) {
+            while (true) {
+                token = tokenizer.requireToken();
+                if ("{".equals(token)) {
+                    break;
+                } else {
+                    /// TODO
+                    if (!",".equals(token)) {
+                        api.mapClassToInterface(cl, token);
+                    }
+                }
+            }
+        }
+        if ("java.lang.Enum".equals(ext)) {
+            cl.setIsEnum(true);
+        } else if (isAnnotation) {
+            api.mapClassToInterface(cl, "java.lang.annotation.Annotation");
+        } else if (api.implementsInterface(cl, "java.lang.annotation.Annotation")) {
+            cl.setIsAnnotationType(true);
+        }
+        if (!"{".equals(token)) {
+            throw new ApiParseException("expected {", tokenizer.getLine());
+        }
+        token = tokenizer.requireToken();
+        while (true) {
+            if ("}".equals(token)) {
+                break;
+            } else if ("ctor".equals(token)) {
+                token = tokenizer.requireToken();
+                parseConstructor(api, tokenizer, cl, token);
+            } else if ("method".equals(token)) {
+                token = tokenizer.requireToken();
+                parseMethod(api, tokenizer, cl, token);
+            } else if ("field".equals(token)) {
+                token = tokenizer.requireToken();
+                parseField(api, tokenizer, cl, token, false);
+            } else if ("enum_constant".equals(token)) {
+                token = tokenizer.requireToken();
+                parseField(api, tokenizer, cl, token, true);
+            } else {
+                throw new ApiParseException("expected ctor, enum_constant, field or method", tokenizer.getLine());
+            }
+            token = tokenizer.requireToken();
+        }
+        pkg.addClass(cl);
+    }
+
+    private static Pair<String, List<String>> processKotlinTypeSuffix(ApiInfo api, String token, List<String> annotations) throws ApiParseException {
+        if (api.getKotlinStyleNulls()) {
+            if (token.endsWith("?")) {
+                token = token.substring(0, token.length() - 1);
+                annotations = mergeAnnotations(annotations, Extractor.SUPPORT_NULLABLE);
+            } else if (token.endsWith("!")) {
+                token = token.substring(0, token.length() - 1);
+            } else if (!token.endsWith("!")) {
+                if (!TextTypeItem.Companion.isPrimitive(token)) { // Don't add nullness on primitive types like void
+                    annotations = mergeAnnotations(annotations, Extractor.SUPPORT_NOTNULL);
+                }
+            }
+        } else if (token.endsWith("?") || token.endsWith("!")) {
+            throw new ApiParseException("Did you forget to supply --input-kotlin-nulls? Found Kotlin-style null type suffix when parser was not configured " +
+                    "to interpret signature file that way: " + token);
+        }
+        //noinspection unchecked
+        return new Pair<>(token, annotations);
+    }
+
+    private static Pair<String, List<String>> getAnnotations(Tokenizer tokenizer, String token) throws ApiParseException {
+        List<String> annotations = null;
+
+        while (true) {
+            if (token.startsWith("@")) {
+                // Annotation
+                String annotation = token;
+                if (annotation.indexOf('.') == -1) {
+                    // Restore annotations that were shortened on export
+                    annotation = AnnotationItem.Companion.unshortenAnnotation(annotation);
+                }
+                token = tokenizer.requireToken();
+                if (token.equals("(")) {
+                    // Annotation arguments
+                    int start = tokenizer.offset() - 1;
+                    while (!token.equals(")")) {
+                        token = tokenizer.requireToken();
+                    }
+                    annotation += tokenizer.getStringFromOffset(start);
+                    token = tokenizer.requireToken();
+                }
+                if (annotations == null) {
+                    annotations = new ArrayList<>();
+                }
+                annotations.add(annotation);
+            } else {
+                break;
+            }
+        }
+
+        if (annotations != null) {
+            //noinspection unchecked
+            return new Pair<>(token, annotations);
+        } else {
+            return null;
+        }
+    }
+
+    private static void parseConstructor(ApiInfo api, Tokenizer tokenizer, TextClassItem cl, String token)
+            throws ApiParseException {
+        boolean pub = false;
+        boolean prot = false;
+        boolean priv = false;
+        boolean dep = false;
+        String name;
+        TextConstructorItem method;
+
+        // Metalava: including annotations in file now
+        List<String> annotations = null;
+        Pair<String, List<String>> result = getAnnotations(tokenizer, token);
+        if (result != null) {
+            token = result.component1();
+            annotations = result.component2();
+        }
+
+        if ("public".equals(token)) {
+            pub = true;
+            token = tokenizer.requireToken();
+        } else if ("protected".equals(token)) {
+            prot = true;
+            token = tokenizer.requireToken();
+        } else if ("private".equals(token)) {
+            priv = true;
+            token = tokenizer.requireToken();
+        }
+        if ("deprecated".equals(token)) {
+            dep = true;
+            token = tokenizer.requireToken();
+        }
+        assertIdent(tokenizer, token);
+        name = token.substring(token.lastIndexOf('.') + 1); // For inner classes, strip outer classes from name
+        token = tokenizer.requireToken();
+        if (!"(".equals(token)) {
+            throw new ApiParseException("expected (", tokenizer.getLine());
+        }
+        method = new TextConstructorItem(api, /*typeParameters*/
+                name, /*signature*/ cl, pub, prot, priv, false/*isFinal*/,
+                false/*isStatic*/, /*isSynthetic*/ false/*isAbstract*/, false/*isSynthetic*/,
+                false/*isNative*/, false/* isDefault */,
+                /*isAnnotationElement*/  /*flatSignature*/
+                /*overriddenMethod*/ cl.asTypeInfo(),
+                /*thrownExceptions*/ tokenizer.pos(), annotations);
+        method.setDeprecated(dep);
+        token = tokenizer.requireToken();
+        parseParameterList(api, tokenizer, method, /*new HashSet<String>(),*/ token);
+        token = tokenizer.requireToken();
+        if ("throws".equals(token)) {
+            token = parseThrows(tokenizer, method);
+        }
+        if (!";".equals(token)) {
+            throw new ApiParseException("expected ; found " + token, tokenizer.getLine());
+        }
+        cl.addConstructor(method);
+    }
+
+    private static void parseMethod(ApiInfo api, Tokenizer tokenizer, TextClassItem cl, String token)
+            throws ApiParseException {
+        boolean pub = false;
+        boolean prot = false;
+        boolean priv = false;
+        boolean stat = false;
+        boolean fin = false;
+        boolean abs = false;
+        boolean dep = false;
+        boolean syn = false;
+        boolean def = false;
+        TextTypeItem returnType;
+        String name;
+        TextMethodItem method;
+        String typeParameterList = null;
+
+        // Metalava: including annotations in file now
+        List<String> annotations = null;
+        Pair<String, List<String>> result = getAnnotations(tokenizer, token);
+        if (result != null) {
+            token = result.component1();
+            annotations = result.component2();
+        }
+
+        if ("public".equals(token)) {
+            pub = true;
+            token = tokenizer.requireToken();
+        } else if ("protected".equals(token)) {
+            prot = true;
+            token = tokenizer.requireToken();
+        } else if ("private".equals(token)) {
+            priv = true;
+            token = tokenizer.requireToken();
+        }
+        if ("default".equals(token)) {
+            def = true;
+            token = tokenizer.requireToken();
+        }
+        if ("static".equals(token)) {
+            stat = true;
+            token = tokenizer.requireToken();
+        }
+        if ("final".equals(token)) {
+            fin = true;
+            token = tokenizer.requireToken();
+        }
+        if ("abstract".equals(token)) {
+            abs = true;
+            token = tokenizer.requireToken();
+        }
+        if ("deprecated".equals(token)) {
+            dep = true;
+            token = tokenizer.requireToken();
+        }
+        if ("synchronized".equals(token)) {
+            syn = true;
+            token = tokenizer.requireToken();
+        }
+        if ("<".equals(token)) {
+            typeParameterList = parseTypeParameterList(tokenizer);
+            token = tokenizer.requireToken();
+        }
+        assertIdent(tokenizer, token);
+
+        Pair<String, List<String>> kotlinTypeSuffix = processKotlinTypeSuffix(api, token, annotations);
+        token = kotlinTypeSuffix.getFirst();
+        annotations = kotlinTypeSuffix.getSecond();
+        returnType = api.obtainTypeFromString(token);
+
+        token = tokenizer.requireToken();
+        assertIdent(tokenizer, token);
+        name = token;
+        method = new TextMethodItem(
+                api, name, /*signature*/ cl,
+                pub, prot, priv, fin, stat, abs/*isAbstract*/,
+                syn, false/*isNative*/, def/*isDefault*/,
+                returnType, tokenizer.pos(), annotations);
+        method.setDeprecated(dep);
+        method.setTypeParameterList(typeParameterList);
+        token = tokenizer.requireToken();
+        if (!"(".equals(token)) {
+            throw new ApiParseException("expected (", tokenizer.getLine());
+        }
+        token = tokenizer.requireToken();
+        parseParameterList(api, tokenizer, method, /*typeVariableNames,*/ token);
+        token = tokenizer.requireToken();
+        if ("throws".equals(token)) {
+            token = parseThrows(tokenizer, method);
+        }
+        if (!";".equals(token)) {
+            throw new ApiParseException("expected ; found " + token, tokenizer.getLine());
+        }
+        cl.addMethod(method);
+    }
+
+    private static List<String> mergeAnnotations(List<String> annotations, String annotation) {
+        if (annotations == null) {
+            annotations = new ArrayList<>();
+        }
+        annotations.add("@" + annotation);
+        return annotations;
+    }
+
+    private static void parseField(ApiInfo api, Tokenizer tokenizer, TextClassItem cl, String token, boolean isEnum)
+            throws ApiParseException {
+        boolean pub = false;
+        boolean prot = false;
+        boolean priv = false;
+        boolean stat = false;
+        boolean fin = false;
+        boolean dep = false;
+        boolean trans = false;
+        boolean vol = false;
+        String type;
+        String name;
+        String val = null;
+        Object v;
+        TextFieldItem field;
+
+        // Metalava: including annotations in file now
+        List<String> annotations = null;
+        Pair<String, List<String>> result = getAnnotations(tokenizer, token);
+        if (result != null) {
+            token = result.component1();
+            annotations = result.component2();
+        }
+
+        if ("public".equals(token)) {
+            pub = true;
+            token = tokenizer.requireToken();
+        } else if ("protected".equals(token)) {
+            prot = true;
+            token = tokenizer.requireToken();
+        } else if ("private".equals(token)) {
+            priv = true;
+            token = tokenizer.requireToken();
+        }
+        if ("static".equals(token)) {
+            stat = true;
+            token = tokenizer.requireToken();
+        }
+        if ("final".equals(token)) {
+            fin = true;
+            token = tokenizer.requireToken();
+        }
+        if ("deprecated".equals(token)) {
+            dep = true;
+            token = tokenizer.requireToken();
+        }
+        if ("transient".equals(token)) {
+            trans = true;
+            token = tokenizer.requireToken();
+        }
+        if ("volatile".equals(token)) {
+            vol = true;
+            token = tokenizer.requireToken();
+        }
+        assertIdent(tokenizer, token);
+
+        Pair<String, List<String>> kotlinTypeSuffix = processKotlinTypeSuffix(api, token, annotations);
+        token = kotlinTypeSuffix.getFirst();
+        annotations = kotlinTypeSuffix.getSecond();
+        type = token;
+        TextTypeItem typeInfo = api.obtainTypeFromString(type);
+
+        token = tokenizer.requireToken();
+        assertIdent(tokenizer, token);
+        name = token;
+        token = tokenizer.requireToken();
+        if ("=".equals(token)) {
+            token = tokenizer.requireToken(false);
+            val = token;
+            token = tokenizer.requireToken();
+        }
+        if (!";".equals(token)) {
+            throw new ApiParseException("expected ; found " + token, tokenizer.getLine());
+        }
+        try {
+            v = parseValue(type, val);
+        } catch (ApiParseException ex) {
+            ex.line = tokenizer.getLine();
+            throw ex;
+        }
+
+        field = new TextFieldItem(api, name, cl, pub, prot, priv, fin, stat,
+                trans, vol, typeInfo, v, tokenizer.pos(),
+                annotations);
+        field.setDeprecated(dep);
+        if (isEnum) {
+            cl.addEnumConstant(field);
+        } else {
+            cl.addField(field);
+        }
+    }
+
+    public static Object parseValue(String type, String val) throws ApiParseException {
+        if (val != null) {
+            if ("boolean".equals(type)) {
+                return "true".equals(val) ? Boolean.TRUE : Boolean.FALSE;
+            } else if ("byte".equals(type)) {
+                return Integer.valueOf(val);
+            } else if ("short".equals(type)) {
+                return Integer.valueOf(val);
+            } else if ("int".equals(type)) {
+                return Integer.valueOf(val);
+            } else if ("long".equals(type)) {
+                return Long.valueOf(val.substring(0, val.length() - 1));
+            } else if ("float".equals(type)) {
+                if ("(1.0f/0.0f)".equals(val) || "(1.0f / 0.0f)".equals(val)) {
+                    return Float.POSITIVE_INFINITY;
+                } else if ("(-1.0f/0.0f)".equals(val) || "(-1.0f / 0.0f)".equals(val)) {
+                    return Float.NEGATIVE_INFINITY;
+                } else if ("(0.0f/0.0f)".equals(val) || "(0.0f / 0.0f)".equals(val)) {
+                    return Float.NaN;
+                } else {
+                    return Float.valueOf(val);
+                }
+            } else if ("double".equals(type)) {
+                if ("(1.0/0.0)".equals(val) || "(1.0 / 0.0)".equals(val)) {
+                    return Double.POSITIVE_INFINITY;
+                } else if ("(-1.0/0.0)".equals(val) || "(-1.0 / 0.0)".equals(val)) {
+                    return Double.NEGATIVE_INFINITY;
+                } else if ("(0.0/0.0)".equals(val) || "(0.0 / 0.0)".equals(val)) {
+                    return Double.NaN;
+                } else {
+                    return Double.valueOf(val);
+                }
+            } else if ("char".equals(type)) {
+                return (char) Integer.parseInt(val);
+            } else if ("java.lang.String".equals(type)) {
+                if ("null".equals(val)) {
+                    return null;
+                } else {
+                    return javaUnescapeString(val.substring(1, val.length() - 1));
+                }
+            }
+        }
+        if ("null".equals(val)) {
+            return null;
+        } else {
+            return val;
+        }
+    }
+
+    private static String parseTypeParameterList(Tokenizer tokenizer) throws ApiParseException {
+        String token;
+
+        int start = tokenizer.offset() - 1;
+        int balance = 1;
+        while (balance > 0) {
+            token = tokenizer.requireToken();
+            if (token.equals("<")) {
+                balance++;
+            } else if (token.equals(">")) {
+                balance--;
+            }
+        }
+
+        return tokenizer.getStringFromOffset(start);
+    }
+
+    private static void parseParameterList(ApiInfo api, Tokenizer tokenizer, TextMethodItem method,
+                                           String token) throws ApiParseException {
+        int index = 0;
+        while (true) {
+            if (")".equals(token)) {
+                return;
+            }
+
+            // Metalava: including annotations in file now
+            List<String> annotations = null;
+            Pair<String, List<String>> result = getAnnotations(tokenizer, token);
+            if (result != null) {
+                token = result.component1();
+                annotations = result.component2();
+            }
+
+            Pair<String, List<String>> kotlinTypeSuffix = processKotlinTypeSuffix(api, token, annotations);
+            token = kotlinTypeSuffix.getFirst();
+            annotations = kotlinTypeSuffix.getSecond();
+            String type = token;
+            TextTypeItem typeInfo = api.obtainTypeFromString(token);
+
+            String name = null;
+            token = tokenizer.requireToken();
+            String publicName;
+            if (isIdent(token)) {
+                name = token;
+                publicName = name;
+                token = tokenizer.requireToken();
+            } else {
+                name = "arg" + (index + 1);
+                publicName = null;
+            }
+            if (",".equals(token)) {
+                token = tokenizer.requireToken();
+            } else if (")".equals(token)) {
+            } else {
+                throw new ApiParseException("expected , found " + token, tokenizer.getLine());
+            }
+
+            method.addParameter(new TextParameterItem(api, method, name, publicName, index, type,
+                    typeInfo,
+                    type.endsWith("..."),
+                    tokenizer.pos(),
+                    annotations));
+            if (type.endsWith("...")) {
+                method.setVarargs(true);
+            }
+            index++;
+        }
+    }
+
+    private static String parseThrows(Tokenizer tokenizer, TextMethodItem method)
+            throws ApiParseException {
+        String token = tokenizer.requireToken();
+        boolean comma = true;
+        while (true) {
+            if (";".equals(token)) {
+                return token;
+            } else if (",".equals(token)) {
+                if (comma) {
+                    throw new ApiParseException("Expected exception, got ','", tokenizer.getLine());
+                }
+                comma = true;
+            } else {
+                if (!comma) {
+                    throw new ApiParseException("Expected ',' or ';' got " + token, tokenizer.getLine());
+                }
+                comma = false;
+                method.addException(token);
+            }
+            token = tokenizer.requireToken();
+        }
+    }
+
+    private static String qualifiedName(String pkg, String className) {
+        return pkg + "." + className;
+    }
+
+    private static boolean isIdent(String token) {
+        return isident(token.charAt(0));
+    }
+
+    private static void assertIdent(Tokenizer tokenizer, String token) throws ApiParseException {
+        if (!isident(token.charAt(0))) {
+            throw new ApiParseException("Expected identifier: " + token, tokenizer.getLine());
+        }
+    }
+
+    static class Tokenizer {
+        char[] mBuf;
+        String mFilename;
+        int mPos;
+        int mLine = 1;
+
+        Tokenizer(String filename, char[] buf) {
+            mFilename = filename;
+            mBuf = buf;
+        }
+
+        public SourcePositionInfo pos() {
+            return new SourcePositionInfo(mFilename, mLine, 0);
+        }
+
+        public int getLine() {
+            return mLine;
+        }
+
+        boolean eatWhitespace() {
+            boolean ate = false;
+            while (mPos < mBuf.length && isspace(mBuf[mPos])) {
+                if (mBuf[mPos] == '\n') {
+                    mLine++;
+                }
+                mPos++;
+                ate = true;
+            }
+            return ate;
+        }
+
+        boolean eatComment() {
+            if (mPos + 1 < mBuf.length) {
+                if (mBuf[mPos] == '/' && mBuf[mPos + 1] == '/') {
+                    mPos += 2;
+                    while (mPos < mBuf.length && !isnewline(mBuf[mPos])) {
+                        mPos++;
+                    }
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        void eatWhitespaceAndComments() {
+            while (eatWhitespace() || eatComment()) {
+            }
+        }
+
+        public String requireToken() throws ApiParseException {
+            return requireToken(true);
+        }
+
+        public String requireToken(boolean parenIsSep) throws ApiParseException {
+            final String token = getToken(parenIsSep);
+            if (token != null) {
+                return token;
+            } else {
+                throw new ApiParseException("Unexpected end of file", mLine);
+            }
+        }
+
+        public String getToken() throws ApiParseException {
+            return getToken(true);
+        }
+
+        public int offset() {
+            return mPos;
+        }
+
+        public String getStringFromOffset(int offset) {
+            return new String(mBuf, offset, mPos - offset);
+        }
+
+        public String getToken(boolean parenIsSep) throws ApiParseException {
+            eatWhitespaceAndComments();
+            if (mPos >= mBuf.length) {
+                return null;
+            }
+            final int line = mLine;
+            final char c = mBuf[mPos];
+            final int start = mPos;
+            mPos++;
+            if (c == '"') {
+                final int STATE_BEGIN = 0;
+                final int STATE_ESCAPE = 1;
+                int state = STATE_BEGIN;
+                while (true) {
+                    if (mPos >= mBuf.length) {
+                        throw new ApiParseException("Unexpected end of file for \" starting at " + line, mLine);
+                    }
+                    final char k = mBuf[mPos];
+                    if (k == '\n' || k == '\r') {
+                        throw new ApiParseException("Unexpected newline for \" starting at " + line, mLine);
+                    }
+                    mPos++;
+                    switch (state) {
+                        case STATE_BEGIN:
+                            switch (k) {
+                                case '\\':
+                                    state = STATE_ESCAPE;
+                                    mPos++;
+                                    break;
+                                case '"':
+                                    return new String(mBuf, start, mPos - start);
+                            }
+                        case STATE_ESCAPE:
+                            state = STATE_BEGIN;
+                            break;
+                    }
+                }
+            } else if (issep(c, parenIsSep)) {
+                return "" + c;
+            } else {
+                int genericDepth = 0;
+                do {
+                    while (mPos < mBuf.length && !isspace(mBuf[mPos]) && !issep(mBuf[mPos], parenIsSep)) {
+                        mPos++;
+                    }
+                    if (mPos < mBuf.length) {
+                        if (mBuf[mPos] == '<') {
+                            genericDepth++;
+                            mPos++;
+                        } else if (genericDepth != 0) {
+                            if (mBuf[mPos] == '>') {
+                                genericDepth--;
+                            }
+                            mPos++;
+                        }
+                    }
+                } while (mPos < mBuf.length
+                        && ((!isspace(mBuf[mPos]) && !issep(mBuf[mPos], parenIsSep)) || genericDepth != 0));
+                if (mPos >= mBuf.length) {
+                    throw new ApiParseException("Unexpected end of file for \" starting at " + line, mLine);
+                }
+                return new String(mBuf, start, mPos - start);
+            }
+        }
+    }
+
+    static boolean isspace(char c) {
+        return c == ' ' || c == '\t' || c == '\n' || c == '\r';
+    }
+
+    static boolean isnewline(char c) {
+        return c == '\n' || c == '\r';
+    }
+
+    static boolean issep(char c, boolean parenIsSep) {
+        if (parenIsSep) {
+            if (c == '(' || c == ')') {
+                return true;
+            }
+        }
+        return c == '{' || c == '}' || c == ',' || c == ';' || c == '<' || c == '>';
+    }
+
+    private static boolean isident(char c) {
+        if (c == '"' || issep(c, true)) {
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/src/main/java/com/android/tools/metalava/doclava1/ApiInfo.kt b/src/main/java/com/android/tools/metalava/doclava1/ApiInfo.kt
new file mode 100644
index 0000000..1993aa5
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/doclava1/ApiInfo.kt
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2010 Google 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.
+ */
+
+package com.android.tools.metalava.doclava1
+
+import com.android.annotations.NonNull
+import com.android.tools.metalava.CodebaseComparator
+import com.android.tools.metalava.ComparisonVisitor
+import com.android.tools.metalava.model.AnnotationItem
+import com.android.tools.metalava.model.ClassItem
+import com.android.tools.metalava.model.Codebase
+import com.android.tools.metalava.model.DefaultCodebase
+import com.android.tools.metalava.model.Item
+import com.android.tools.metalava.model.PackageItem
+import com.android.tools.metalava.model.PackageList
+import com.android.tools.metalava.model.text.TextBackedAnnotationItem
+import com.android.tools.metalava.model.text.TextClassItem
+import com.android.tools.metalava.model.text.TextMethodItem
+import com.android.tools.metalava.model.text.TextPackageItem
+import com.android.tools.metalava.model.text.TextTypeItem
+import com.android.tools.metalava.model.visitors.ItemVisitor
+import com.android.tools.metalava.model.visitors.TypeVisitor
+import java.util.ArrayList
+import java.util.HashMap
+import java.util.function.Predicate
+
+// Copy of ApiInfo in doclava1 (converted to Kotlin + some cleanup to make it work with metalava's data structures.
+// (Converted to Kotlin such that I can inherit behavior via interfaces, in particular Codebase.)
+class ApiInfo : DefaultCodebase() {
+    /**
+     * Whether types should be interpreted to be in Kotlin format (e.g. ? suffix means nullable,
+     * ! suffix means unknown, and absence of a suffix means not nullable.
+     */
+    var kotlinStyleNulls = false
+
+    private val mPackages = HashMap<String, TextPackageItem>(300)
+    private val mAllClasses = HashMap<String, TextClassItem>(30000)
+    private val mClassToSuper = HashMap<TextClassItem, String>(30000)
+    private val mClassToInterface = HashMap<TextClassItem, ArrayList<String>>(10000)
+
+    override var description = "Codebase"
+
+    override fun trustedApi(): Boolean = true
+
+    override fun getPackages(): PackageList {
+        val list = ArrayList<PackageItem>(mPackages.values)
+        list.sortWith(PackageItem.comparator)
+        return PackageList(list)
+    }
+
+    override fun size(): Int {
+        return mPackages.size
+    }
+
+    override fun findClass(@NonNull className: String): TextClassItem? {
+        return mAllClasses[className]
+    }
+
+    private fun resolveInterfaces() {
+        for (cl in mAllClasses.values) {
+            val ifaces = mClassToInterface[cl] ?: continue
+            for (iface in ifaces) {
+                var ci: TextClassItem? = mAllClasses[iface]
+                if (ci == null) {
+                    // Interface not provided by this codebase. Inject a stub.
+                    ci = TextClassItem.createInterfaceStub(this, iface)
+                }
+                cl.addInterface(ci)
+            }
+        }
+    }
+
+    override fun supportsDocumentation(): Boolean = false
+
+    fun mapClassToSuper(classInfo: TextClassItem, superclass: String?) {
+        superclass?.let { mClassToSuper.put(classInfo, superclass) }
+    }
+
+    fun mapClassToInterface(classInfo: TextClassItem, iface: String) {
+        if (!mClassToInterface.containsKey(classInfo)) {
+            mClassToInterface.put(classInfo, ArrayList())
+        }
+        mClassToInterface[classInfo]?.add(iface)
+    }
+
+    fun implementsInterface(classInfo: TextClassItem, iface: String): Boolean {
+        return mClassToInterface[classInfo]?.contains(iface) ?: false
+    }
+
+    fun addPackage(pInfo: TextPackageItem) {
+        // track the set of organized packages in the API
+        mPackages.put(pInfo.name(), pInfo)
+
+        // accumulate a direct map of all the classes in the API
+        for (cl in pInfo.allClasses()) {
+            mAllClasses.put(cl.qualifiedName(), cl as TextClassItem)
+        }
+    }
+
+    private fun resolveSuperclasses() {
+        for (cl in mAllClasses.values) {
+            // java.lang.Object has no superclass
+            if (cl.isJavaLangObject()) {
+                continue
+            }
+            var scName: String? = mClassToSuper[cl]
+            if (scName == null) {
+                scName = "java.lang.Object"
+            }
+            var superclass: TextClassItem? = mAllClasses[scName]
+            if (superclass == null) {
+                // Superclass not provided by this codebase. Inject a stub.
+                superclass = TextClassItem.createClassStub(this, scName)
+            }
+            cl.setSuperClass(superclass)
+        }
+    }
+
+    private fun resolveThrowsClasses() {
+        for (cl in mAllClasses.values) {
+            for (methodItem in cl.methods()) {
+                val methodInfo = methodItem as TextMethodItem
+                val names = methodInfo.throwsTypeNames()
+                if (!names.isEmpty()) {
+                    val result = ArrayList<TextClassItem>()
+                    for (exception in names) {
+                        var exceptionClass: TextClassItem? = mAllClasses[exception]
+                        if (exceptionClass == null) {
+                            // Exception not provided by this codebase. Inject a stub.
+                            exceptionClass = TextClassItem.createClassStub(
+                                this, exception
+                            )
+                        }
+                        result.add(exceptionClass)
+                    }
+                    methodInfo.setThrowsList(result)
+                }
+            }
+
+            // java.lang.Object has no superclass
+            var scName: String? = mClassToSuper[cl]
+            if (scName == null) {
+                scName = "java.lang.Object"
+            }
+            var superclass: TextClassItem? = mAllClasses[scName]
+            if (superclass == null) {
+                // Superclass not provided by this codebase. Inject a stub.
+                superclass = TextClassItem.createClassStub(this, scName)
+            }
+            cl.setSuperClass(superclass)
+        }
+    }
+
+    private fun resolveInnerClasses() {
+        mPackages.values
+            .asSequence()
+            .map { it.classList().listIterator() as MutableListIterator<ClassItem> }
+            .forEach {
+                while (it.hasNext()) {
+                    val cl = it.next() as TextClassItem
+                    val name = cl.name
+                    var index = name.lastIndexOf('.')
+                    if (index != -1) {
+                        cl.name = name.substring(index + 1)
+                        val qualifiedName = cl.qualifiedName
+                        index = qualifiedName.lastIndexOf('.')
+                        assert(index != -1) { qualifiedName }
+                        val outerClassName = qualifiedName.substring(0, index)
+                        val outerClass = mAllClasses[outerClassName]!!
+                        cl.containingClass = outerClass
+                        outerClass.addInnerClass(cl)
+
+                        // Should no longer be listed as top level
+                        it.remove()
+                    }
+                }
+            }
+    }
+
+    fun postProcess() {
+        resolveSuperclasses()
+        resolveInterfaces()
+        resolveThrowsClasses()
+        resolveInnerClasses()
+    }
+
+    override fun findPackage(pkgName: String): PackageItem? {
+        return mPackages.values.firstOrNull { pkgName == it.qualifiedName() }
+    }
+
+    override fun accept(visitor: ItemVisitor) {
+        getPackages().accept(visitor)
+    }
+
+    override fun acceptTypes(visitor: TypeVisitor) {
+        getPackages().acceptTypes(visitor)
+    }
+
+    override fun compareWith(visitor: ComparisonVisitor, other: Codebase, filter: Predicate<Item>?) {
+        CodebaseComparator().compare(visitor, this, other, filter)
+    }
+
+    override fun createAnnotation(source: String, context: Item?, mapName: Boolean): AnnotationItem {
+        return TextBackedAnnotationItem(this, source, mapName)
+    }
+
+    override fun toString(): String {
+        return description
+    }
+
+    override fun unsupported(desc: String?): Nothing {
+        error(desc ?: "Not supported for a signature-file based codebase")
+    }
+
+    override fun filter(filterEmit: Predicate<Item>, filterReference: Predicate<Item>): Codebase {
+        unsupported()
+    }
+
+    // Copied from Converter:
+
+    fun obtainTypeFromString(type: String): TextTypeItem {
+        return mTypesFromString.obtain(type) as TextTypeItem
+    }
+
+    private val mTypesFromString = object : Cache(this) {
+        override fun make(o: Any): Any {
+            val name = o as String
+
+            return TextTypeItem(codebase, name)
+        }
+    }
+
+    private abstract class Cache(val codebase: ApiInfo) {
+
+        protected var mCache = HashMap<Any, Any>()
+
+        internal fun obtain(o: Any?): Any? {
+            if (o == null) {
+                return null
+            }
+            var r: Any? = mCache[o]
+            if (r == null) {
+                r = make(o)
+                mCache.put(o, r)
+            }
+            return r
+        }
+
+        protected abstract fun make(o: Any): Any
+    }
+}
diff --git a/src/main/java/com/android/tools/metalava/doclava1/ApiParseException.java b/src/main/java/com/android/tools/metalava/doclava1/ApiParseException.java
new file mode 100644
index 0000000..6711f8c
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/doclava1/ApiParseException.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2010 Google 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.
+ */
+
+package com.android.tools.metalava.doclava1;
+
+//
+// Copied from doclava1, but adapted to metalava's code model
+//
+public final class ApiParseException extends Exception {
+    public String file;
+    public int line;
+
+    public ApiParseException(String message) {
+        super(message);
+    }
+
+    public ApiParseException(String message, Exception cause) {
+        super(message, cause);
+        if (cause instanceof ApiParseException) {
+            this.line = ((ApiParseException) cause).line;
+        }
+    }
+
+    public ApiParseException(String message, int line) {
+        super(message);
+        this.line = line;
+    }
+
+    public String getMessage() {
+        if (line > 0) {
+            return super.getMessage() + " line " + line;
+        } else {
+            return super.getMessage();
+        }
+    }
+}
diff --git a/src/main/java/com/android/tools/metalava/doclava1/ApiPredicate.kt b/src/main/java/com/android/tools/metalava/doclava1/ApiPredicate.kt
new file mode 100644
index 0000000..4b5af4c
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/doclava1/ApiPredicate.kt
@@ -0,0 +1,103 @@
+package com.android.tools.metalava.doclava1
+
+import com.android.tools.metalava.Options
+import com.android.tools.metalava.model.ClassItem
+import com.android.tools.metalava.model.Codebase
+import com.android.tools.metalava.model.Item
+import com.android.tools.metalava.model.MemberItem
+import com.android.tools.metalava.model.PackageItem
+import com.android.tools.metalava.options
+import java.util.function.Predicate
+
+// Ported from doclava1
+
+/**
+ * Predicate that decides if the given member should be considered part of an
+ * API surface area. To make the most accurate decision, it searches for
+ * signals on the member, all containing classes, and all containing packages.
+ */
+class ApiPredicate(
+    val codebase: Codebase,
+    /**
+     * Set if the value of [MemberItem.hasShowAnnotation] should be
+     * ignored. That is, this predicate will assume that all encountered members
+     * match the "shown" requirement.
+     *
+     * This is typically useful when generating "current.txt", when no
+     * [Options.showAnnotations] have been defined.
+     */
+    private val ignoreShown: Boolean = options.showUnannotated,
+
+    /**
+     * Set if the value of [MemberItem.removed] should be ignored.
+     * That is, this predicate will assume that all encountered members match
+     * the "removed" requirement.
+     *
+     * This is typically useful when generating "removed.txt", when it's okay to
+     * reference both current and removed APIs.
+     */
+    private val ignoreRemoved: Boolean = false,
+
+    /**
+     * Set what the value of [MemberItem.removed] must be equal to in
+     * order for a member to match.
+     *
+     * This is typically useful when generating "removed.txt", when you only
+     * want to match members that have actually been removed.
+     */
+    private val matchRemoved: Boolean = false,
+
+    /** Whether we allow matching items loaded from jar files instead of sources */
+    private val allowFromJar: Boolean = true
+) : Predicate<Item> {
+
+    override fun test(member: Item): Boolean {
+        if (!allowFromJar && member.isFromClassPath()) {
+            return false
+        }
+
+        var visible = member.isPublic || member.isProtected
+        var hidden = member.hidden
+        if (!visible || hidden) {
+            return false
+        }
+
+        var hasShowAnnotation = ignoreShown || member.hasShowAnnotation()
+        var docOnly = member.docOnly
+        var removed = member.removed
+
+        var clazz: ClassItem? = when (member) {
+            is MemberItem -> member.containingClass()
+            is ClassItem -> member
+            else -> null
+        }
+
+        if (clazz != null) {
+            var pkg: PackageItem? = clazz.containingPackage()
+            while (pkg != null) {
+                hidden = hidden or pkg.hidden
+                docOnly = docOnly or pkg.docOnly
+                removed = removed or pkg.removed
+                pkg = pkg.containingPackage()
+            }
+        }
+        while (clazz != null) {
+            visible = visible and (clazz.isPublic || clazz.isProtected)
+            hasShowAnnotation = hasShowAnnotation or (ignoreShown || clazz.hasShowAnnotation())
+            hidden = hidden or clazz.hidden
+            docOnly = docOnly or clazz.docOnly
+            removed = removed or clazz.removed
+            clazz = clazz.containingClass()
+        }
+
+        if (ignoreRemoved) {
+            removed = matchRemoved
+        }
+
+        if (docOnly && options.includeDocOnly) {
+            docOnly = false
+        }
+
+        return visible && hasShowAnnotation && !hidden && !docOnly && removed == matchRemoved
+    }
+}
diff --git a/src/main/java/com/android/tools/metalava/doclava1/ElidingPredicate.kt b/src/main/java/com/android/tools/metalava/doclava1/ElidingPredicate.kt
new file mode 100644
index 0000000..3f7c9d7
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/doclava1/ElidingPredicate.kt
@@ -0,0 +1,31 @@
+package com.android.tools.metalava.doclava1
+
+import com.android.tools.metalava.model.Item
+import com.android.tools.metalava.model.MethodItem
+
+import java.util.function.Predicate
+
+// Ported from doclava1
+
+/**
+ * Filter that will elide exact duplicate methods that are already included
+ * in another superclass/interfaces.
+ */
+class ElidingPredicate(private val wrapped: Predicate<Item>) : Predicate<Item> {
+
+    override fun test(method: Item): Boolean {
+        // This method should be included, but if it's an exact duplicate
+        // override then we can elide it.
+        return if (method is MethodItem && !method.isConstructor()) {
+            val differentSuper = method.findPredicateSuperMethod(Predicate { test ->
+                // We're looking for included and perfect signature
+                wrapped.test(test) &&
+                        test is MethodItem &&
+                        MethodItem.sameSignature(method, test, false)
+            })
+            differentSuper == null
+        } else {
+            true
+        }
+    }
+}
diff --git a/src/main/java/com/android/tools/metalava/doclava1/Errors.java b/src/main/java/com/android/tools/metalava/doclava1/Errors.java
new file mode 100644
index 0000000..b346272
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/doclava1/Errors.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+package com.android.tools.metalava.doclava1;
+
+import com.android.annotations.Nullable;
+import com.android.tools.metalava.Severity;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+import static com.android.sdklib.SdkVersionInfo.underlinesToCamelCase;
+import static com.android.tools.metalava.Severity.ERROR;
+import static com.android.tools.metalava.Severity.HIDDEN;
+import static com.android.tools.metalava.Severity.INHERIT;
+import static com.android.tools.metalava.Severity.LINT;
+import static com.android.tools.metalava.Severity.WARNING;
+
+// Copied from doclava1 (and a bunch of stuff left alone; preserving to have same error id's)
+public class Errors {
+    public static class Error {
+        public final int code;
+
+        private Severity level;
+        private Severity defaultLevel;
+
+        /**
+         * The name of this error if known
+         */
+        @Nullable
+        public String name;
+
+        /**
+         * When {@code level} is set to {@link Severity#INHERIT}, this is the parent from
+         * which the error will inherit its level.
+         */
+        private final Error parent;
+
+        public Error(int code, Severity level) {
+            this.code = code;
+            this.level = level;
+            this.defaultLevel = level;
+            this.parent = null;
+            sErrors.add(this);
+        }
+
+        public Error(int code, Error parent) {
+            this.code = code;
+            this.level = Severity.INHERIT;
+            this.defaultLevel = Severity.INHERIT;
+            this.parent = parent;
+            sErrors.add(this);
+        }
+
+        /**
+         * Returns the implied level for this error.
+         * <p>
+         * If the level is {@link Severity#INHERIT}, the level will be returned for the
+         * parent.
+         *
+         * @throws IllegalStateException if the level is {@link Severity#INHERIT} and the
+         *                               parent is {@code null}
+         */
+        public Severity getLevel() {
+            if (level == INHERIT) {
+                if (parent == null) {
+                    throw new IllegalStateException("Error with level INHERIT must have non-null parent");
+                }
+                return parent.getLevel();
+            }
+            return level;
+        }
+
+        /**
+         * Sets the level.
+         * <p>
+         * Valid arguments are:
+         * <ul>
+         * <li>{@link Severity#HIDDEN}
+         * <li>{@link Severity#WARNING}
+         * <li>{@link Severity#ERROR}
+         * </ul>
+         *
+         * @param level the level to set
+         */
+        public void setLevel(Severity level) {
+            if (level == INHERIT) {
+                throw new IllegalArgumentException("Error level may not be set to INHERIT");
+            }
+            this.level = level;
+        }
+
+        public String toString() {
+            return "Error #" + this.code + " (" + this.name + ")";
+        }
+    }
+
+    private static final List<Error> sErrors = new ArrayList<>();
+
+    // Errors for API verification
+    public static final Error PARSE_ERROR = new Error(1, ERROR);
+    public static final Error ADDED_PACKAGE = new Error(2, WARNING);
+    public static final Error ADDED_CLASS = new Error(3, WARNING);
+    public static final Error ADDED_METHOD = new Error(4, WARNING);
+    public static final Error ADDED_FIELD = new Error(5, WARNING);
+    public static final Error ADDED_INTERFACE = new Error(6, WARNING);
+    public static final Error REMOVED_PACKAGE = new Error(7, WARNING);
+    public static final Error REMOVED_CLASS = new Error(8, WARNING);
+    public static final Error REMOVED_METHOD = new Error(9, WARNING);
+    public static final Error REMOVED_FIELD = new Error(10, WARNING);
+    public static final Error REMOVED_INTERFACE = new Error(11, WARNING);
+    public static final Error CHANGED_STATIC = new Error(12, WARNING);
+    public static final Error ADDED_FINAL = new Error(13, WARNING);
+    public static final Error CHANGED_TRANSIENT = new Error(14, WARNING);
+    public static final Error CHANGED_VOLATILE = new Error(15, WARNING);
+    public static final Error CHANGED_TYPE = new Error(16, WARNING);
+    public static final Error CHANGED_VALUE = new Error(17, WARNING);
+    public static final Error CHANGED_SUPERCLASS = new Error(18, WARNING);
+    public static final Error CHANGED_SCOPE = new Error(19, WARNING);
+    public static final Error CHANGED_ABSTRACT = new Error(20, WARNING);
+    public static final Error CHANGED_THROWS = new Error(21, WARNING);
+    public static final Error CHANGED_NATIVE = new Error(22, HIDDEN);
+    public static final Error CHANGED_CLASS = new Error(23, WARNING);
+    public static final Error CHANGED_DEPRECATED = new Error(24, WARNING);
+    public static final Error CHANGED_SYNCHRONIZED = new Error(25, WARNING);
+    public static final Error ADDED_FINAL_UNINSTANTIABLE = new Error(26, WARNING);
+    public static final Error REMOVED_FINAL = new Error(27, WARNING);
+    public static final Error REMOVED_DEPRECATED_CLASS = new Error(28, REMOVED_CLASS);
+    public static final Error REMOVED_DEPRECATED_METHOD = new Error(29, REMOVED_METHOD);
+    public static final Error REMOVED_DEPRECATED_FIELD = new Error(30, REMOVED_FIELD);
+
+
+    // Stuff I've added
+    public static final Error INVALID_NULL_CONVERSION = new Error(40, WARNING);
+    public static final Error PARAMETER_NAME_CHANGE = new Error(41, WARNING);
+
+
+    // Errors in javadoc generation
+    public static final Error UNRESOLVED_LINK = new Error(101, LINT);
+    public static final Error BAD_INCLUDE_TAG = new Error(102, LINT);
+    public static final Error UNKNOWN_TAG = new Error(103, LINT);
+    public static final Error UNKNOWN_PARAM_TAG_NAME = new Error(104, LINT);
+    public static final Error UNDOCUMENTED_PARAMETER = new Error(105, HIDDEN); // LINT
+    public static final Error BAD_ATTR_TAG = new Error(106, LINT);
+    public static final Error BAD_INHERITDOC = new Error(107, HIDDEN); // LINT
+    public static final Error HIDDEN_LINK = new Error(108, LINT);
+    public static final Error HIDDEN_CONSTRUCTOR = new Error(109, WARNING);
+    public static final Error UNAVAILABLE_SYMBOL = new Error(110, WARNING);
+    public static final Error HIDDEN_SUPERCLASS = new Error(111, WARNING);
+    public static final Error DEPRECATED = new Error(112, HIDDEN);
+    public static final Error DEPRECATION_MISMATCH = new Error(113, WARNING);
+    public static final Error MISSING_COMMENT = new Error(114, LINT);
+    public static final Error IO_ERROR = new Error(115, ERROR);
+    public static final Error NO_SINCE_DATA = new Error(116, HIDDEN);
+    public static final Error NO_FEDERATION_DATA = new Error(117, WARNING);
+    public static final Error BROKEN_SINCE_FILE = new Error(118, ERROR);
+    public static final Error INVALID_CONTENT_TYPE = new Error(119, ERROR);
+    public static final Error INVALID_SAMPLE_INDEX = new Error(120, ERROR);
+    public static final Error HIDDEN_TYPE_PARAMETER = new Error(121, WARNING);
+    public static final Error PRIVATE_SUPERCLASS = new Error(122, WARNING);
+    public static final Error NULLABLE = new Error(123, HIDDEN); // LINT
+    public static final Error INT_DEF = new Error(124, HIDDEN); // LINT
+    public static final Error REQUIRES_PERMISSION = new Error(125, LINT);
+    public static final Error BROADCAST_BEHAVIOR = new Error(126, LINT);
+    public static final Error SDK_CONSTANT = new Error(127, LINT);
+    public static final Error TODO = new Error(128, LINT);
+    public static final Error NO_ARTIFACT_DATA = new Error(129, HIDDEN);
+    public static final Error BROKEN_ARTIFACT_FILE = new Error(130, ERROR);
+
+    // Metalava new warnings
+    public static final Error TYPO = new Error(131, LINT);
+    public static final Error MISSING_PERMISSION = new Error(132, LINT);
+    public static final Error MULTIPLE_THREAD_ANNOTATIONS = new Error(133, LINT);
+    public static final Error UNRESOLVED_CLASS = new Error(134, LINT);
+
+    static {
+        // Attempt to initialize error names based on the field names
+        try {
+            for (Field field : Errors.class.getDeclaredFields()) {
+                Object o = field.get(null);
+                if (o instanceof Error) {
+                    Error error = (Error) o;
+                    String fieldName = field.getName();
+                    error.name = underlinesToCamelCase(fieldName.toLowerCase(Locale.US));
+                }
+            }
+        } catch (Throwable unexpected) {
+            unexpected.printStackTrace();
+        }
+    }
+
+    public static boolean setErrorLevel(String id, Severity level) {
+        int code = -1;
+        if (Character.isDigit(id.charAt(0))) {
+            code = Integer.parseInt(id);
+        }
+        for (Error e : sErrors) {
+            if (e.code == code || e.name.equalsIgnoreCase(id)) {
+                e.setLevel(level);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    // Set error severity for all the compatibility related checks
+    public static void enforceCompatibility() {
+        for (Error e : sErrors) {
+            if (e.code >= Errors.PARSE_ERROR.code && e.code <= Errors.PARAMETER_NAME_CHANGE.code) {
+                e.setLevel(ERROR);
+            }
+        }
+    }
+
+    // Primary needed by unit tests; ensure that a previous test doesn't influence
+    // a later one
+    public static void resetLevels() {
+        for (Error error : sErrors) {
+            error.level = error.defaultLevel;
+        }
+    }
+}
diff --git a/src/main/java/com/android/tools/metalava/doclava1/FilterPredicate.kt b/src/main/java/com/android/tools/metalava/doclava1/FilterPredicate.kt
new file mode 100644
index 0000000..6fc7557
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/doclava1/FilterPredicate.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava.doclava1
+
+import com.android.tools.metalava.model.Item
+import com.android.tools.metalava.model.MethodItem
+import java.util.function.Predicate
+
+// Ported from doclava1
+
+class FilterPredicate(private val wrapped: Predicate<Item>) : Predicate<Item> {
+
+    override fun test(method: Item): Boolean {
+        return when {
+            wrapped.test(method) -> true
+            method is MethodItem -> !method.isConstructor() &&
+                    method.findPredicateSuperMethod(wrapped) != null
+            else -> false
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/doclava1/SourcePositionInfo.java b/src/main/java/com/android/tools/metalava/doclava1/SourcePositionInfo.java
new file mode 100644
index 0000000..8418742
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/doclava1/SourcePositionInfo.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava.doclava1;
+
+// Copied from doclava1
+public class SourcePositionInfo implements Comparable {
+    public static final SourcePositionInfo UNKNOWN = new SourcePositionInfo("(unknown)", 0, 0);
+
+    public SourcePositionInfo(String file, int line, int column) {
+        this.file = file;
+        this.line = line;
+        this.column = column;
+    }
+
+    /**
+     * Given this position and str which occurs at that position, as well as str an index into str,
+     * find the SourcePositionInfo.
+     *
+     * @throw StringIndexOutOfBoundsException if index &gt; str.length()
+     */
+    public static SourcePositionInfo add(SourcePositionInfo that, String str, int index) {
+        if (that == null) {
+            return null;
+        }
+        int line = that.line;
+        char prev = 0;
+        for (int i = 0; i < index; i++) {
+            char c = str.charAt(i);
+            if (c == '\r' || (c == '\n' && prev != '\r')) {
+                line++;
+            }
+            prev = c;
+        }
+        return new SourcePositionInfo(that.file, line, 0);
+    }
+
+    @Override
+    public String toString() {
+        return file + ':' + line;
+    }
+
+    public int compareTo(Object o) {
+        SourcePositionInfo that = (SourcePositionInfo) o;
+        int r = this.file.compareTo(that.file);
+        if (r != 0) return r;
+        return this.line - that.line;
+    }
+
+    public String file;
+    public int line;
+    public int column;
+}
diff --git a/src/main/java/com/android/tools/metalava/model/AnnotationItem.kt b/src/main/java/com/android/tools/metalava/model/AnnotationItem.kt
new file mode 100644
index 0000000..962320a
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/model/AnnotationItem.kt
@@ -0,0 +1,456 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava.model
+
+import com.android.SdkConstants
+import com.android.SdkConstants.ATTR_VALUE
+import com.android.tools.metalava.NEWLY_NONNULL
+import com.android.tools.metalava.NEWLY_NULLABLE
+import com.android.tools.metalava.Options
+import com.android.tools.metalava.RECENTLY_NONNULL
+import com.android.tools.metalava.RECENTLY_NULLABLE
+import com.android.tools.metalava.options
+import java.util.function.Predicate
+
+fun isNullableAnnotation(qualifiedName: String): Boolean {
+    return qualifiedName.endsWith("Nullable")
+}
+
+fun isNonNullAnnotation(qualifiedName: String): Boolean {
+    return qualifiedName.endsWith("NonNull") ||
+            qualifiedName.endsWith("NotNull") ||
+            qualifiedName.endsWith("Nonnull")
+}
+
+interface AnnotationItem {
+    val codebase: Codebase
+
+    /** Fully qualified name of the annotation */
+    fun qualifiedName(): String?
+
+    /** Generates source code for this annotation (using fully qualified names) */
+    fun toSource(): String
+
+    /** Whether this annotation is significant and should be included in signature files, stubs, etc */
+    fun isSignificant(): Boolean {
+        return isSignificantAnnotation(qualifiedName() ?: return false)
+    }
+
+    /** Attributes of the annotation (may be empty) */
+    fun attributes(): List<AnnotationAttribute>
+
+    /** True if this annotation represents @Nullable or @NonNull (or some synonymous annotation) */
+    fun isNullnessAnnotation(): Boolean {
+        return isNullable() || isNonNull()
+    }
+
+    /** True if this annotation represents @Nullable (or some synonymous annotation) */
+    fun isNullable(): Boolean {
+        return isNullableAnnotation(qualifiedName() ?: return false)
+    }
+
+    /** True if this annotation represents @NonNull (or some synonymous annotation) */
+    fun isNonNull(): Boolean {
+        return isNonNullAnnotation(qualifiedName() ?: return false)
+    }
+
+    /**
+     * True if this annotation represents a @ParameterName annotation (or some synonymous annotation).
+     * The parameter name should be the default atttribute or "value".
+     */
+    fun isParameterName(): Boolean {
+        return qualifiedName()?.endsWith(".ParameterName") ?: return false
+    }
+
+    /** Returns the given named attribute if specified */
+    fun findAttribute(name: String?): AnnotationAttribute? {
+        val actualName = name ?: ATTR_VALUE
+        return attributes().firstOrNull { it.name == actualName }
+    }
+
+    /** Find the class declaration for the given annotation */
+    fun resolve(): ClassItem? {
+        return codebase.findClass(qualifiedName() ?: return null)
+    }
+
+    companion object {
+        /** Whether the given annotation name is "significant", e.g. should be included in signature files */
+        fun isSignificantAnnotation(qualifiedName: String?): Boolean {
+            return qualifiedName?.startsWith("android.support.annotation.") ?: false
+        }
+
+        /** The simple name of an annotation, which is the annotation name (not qualified name) prefixed by @ */
+        fun simpleName(item: AnnotationItem): String {
+            val qualifiedName = item.qualifiedName() ?: return ""
+            return "@${qualifiedName.substring(qualifiedName.lastIndexOf('.') + 1)}"
+        }
+
+        /**
+         * Maps an annotation name to the name to be used in signatures/stubs/external annotation files.
+         * Annotations that should not be exported are mapped to null.
+         */
+        fun mapName(codebase: Codebase, qualifiedName: String?, filter: Predicate<Item>? = null): String? {
+            qualifiedName ?: return null
+
+            when (qualifiedName) {
+            // Resource annotations
+                "android.annotation.AnimRes" -> return "android.support.annotation.AnimRes"
+                "android.annotation.AnimatorRes" -> return "android.support.annotation.AnimatorRes"
+                "android.annotation.AnyRes" -> return "android.support.annotation.AnyRes"
+                "android.annotation.ArrayRes" -> return "android.support.annotation.ArrayRes"
+                "android.annotation.AttrRes" -> return "android.support.annotation.AttrRes"
+                "android.annotation.BoolRes" -> return "android.support.annotation.BoolRes"
+                "android.annotation.ColorRes" -> return "android.support.annotation.ColorRes"
+                "android.annotation.DimenRes" -> return "android.support.annotation.DimenRes"
+                "android.annotation.DrawableRes" -> return "android.support.annotation.DrawableRes"
+                "android.annotation.FontRes" -> return "android.support.annotation.FontRes"
+                "android.annotation.FractionRes" -> return "android.support.annotation.FractionRes"
+                "android.annotation.IdRes" -> return "android.support.annotation.IdRes"
+                "android.annotation.IntegerRes" -> return "android.support.annotation.IntegerRes"
+                "android.annotation.InterpolatorRes" -> return "android.support.annotation.InterpolatorRes"
+                "android.annotation.LayoutRes" -> return "android.support.annotation.LayoutRes"
+                "android.annotation.MenuRes" -> return "android.support.annotation.MenuRes"
+                "android.annotation.PluralsRes" -> return "android.support.annotation.PluralsRes"
+                "android.annotation.RawRes" -> return "android.support.annotation.RawRes"
+                "android.annotation.StringRes" -> return "android.support.annotation.StringRes"
+                "android.annotation.StyleRes" -> return "android.support.annotation.StyleRes"
+                "android.annotation.StyleableRes" -> return "android.support.annotation.StyleableRes"
+                "android.annotation.TransitionRes" -> return "android.support.annotation.TransitionRes"
+                "android.annotation.XmlRes" -> return "android.support.annotation.XmlRes"
+
+            // Threading
+                "android.annotation.AnyThread" -> return "android.support.annotation.AnyThread"
+                "android.annotation.BinderThread" -> return "android.support.annotation.BinderThread"
+                "android.annotation.MainThread" -> return "android.support.annotation.MainThread"
+                "android.annotation.UiThread" -> return "android.support.annotation.UiThread"
+                "android.annotation.WorkerThread" -> return "android.support.annotation.WorkerThread"
+
+            // Colors
+                "android.annotation.ColorInt" -> return "android.support.annotation.ColorInt"
+                "android.annotation.ColorLong" -> return "android.support.annotation.ColorLong"
+                "android.annotation.HalfFloat" -> return "android.support.annotation.HalfFloat"
+
+            // Ranges and sizes
+                "android.annotation.FloatRange" -> return "android.support.annotation.FloatRange"
+                "android.annotation.IntRange" -> return "android.support.annotation.IntRange"
+                "android.annotation.Size" -> return "android.support.annotation.Size"
+                "android.annotation.Px" -> return "android.support.annotation.Px"
+                "android.annotation.Dimension" -> return "android.support.annotation.Dimension"
+
+            // Null
+                "android.annotation.NonNull" -> return "android.support.annotation.NonNull"
+                "android.annotation.Nullable" -> return "android.support.annotation.Nullable"
+                "libcore.util.NonNull" -> return "android.support.annotation.NonNull"
+                "libcore.util.Nullable" -> return "android.support.annotation.Nullable"
+
+            // Typedefs
+                "android.annotation.IntDef" -> return "android.support.annotation.IntDef"
+                "android.annotation.StringDef" -> return "android.support.annotation.StringDef"
+
+            // Misc
+                "android.annotation.CallSuper" -> return "android.support.annotation.CallSuper"
+                "android.annotation.CheckResult" -> return "android.support.annotation.CheckResult"
+                "android.annotation.RequiresPermission" -> return "android.support.annotation.RequiresPermission"
+
+            // These aren't support annotations, but could/should be:
+                "android.annotation.CurrentTimeMillisLong",
+                "android.annotation.DurationMillisLong",
+                "android.annotation.ElapsedRealtimeLong",
+                "android.annotation.UserIdInt",
+                "android.annotation.BytesLong",
+
+                    // These aren't support annotations
+                "android.annotation.AppIdInt",
+                "android.annotation.BroadcastBehavior",
+                "android.annotation.SdkConstant",
+                "android.annotation.SuppressAutoDoc",
+                "android.annotation.SystemApi",
+                "android.annotation.TestApi",
+                "android.annotation.CallbackExecutor",
+                "android.annotation.Condemned",
+
+                "android.annotation.Widget" -> {
+                    // Remove, unless (a) public or (b) specifically included in --showAnnotations
+                    return if (options.showAnnotations.contains(qualifiedName)) {
+                        qualifiedName
+                    } else if (filter != null) {
+                        val cls = codebase.findClass(qualifiedName)
+                        if (cls != null && filter.test(cls)) {
+                            qualifiedName
+                        } else {
+                            null
+                        }
+                    } else {
+                        qualifiedName
+                    }
+                }
+
+            // Included for analysis, but should not be exported:
+                "android.annotation.SystemService" -> return qualifiedName
+
+            // Should not be mapped to a different package name:
+                "android.annotation.TargetApi",
+                "android.annotation.SuppressLint" -> return qualifiedName
+
+            // We only change recently/newly nullable annotation if the codebase supports it
+                NEWLY_NULLABLE, RECENTLY_NULLABLE -> return if (codebase.supportsStagedNullability) qualifiedName else "android.support.annotation.Nullable"
+                NEWLY_NONNULL, RECENTLY_NONNULL -> return if (codebase.supportsStagedNullability) qualifiedName else "android.support.annotation.NonNull"
+
+                else -> {
+                    // Some new annotations added to the platform: assume they are support annotations?
+                    return when {
+                    // Special Kotlin annotations recognized by the compiler: map to supported package name
+                        qualifiedName.endsWith(".ParameterName") || qualifiedName.endsWith(".DefaultValue") ->
+                            "kotlin.annotations.jvm.internal${qualifiedName.substring(qualifiedName.lastIndexOf('.'))}"
+
+                    // Other third party nullness annotations?
+                        isNullableAnnotation(qualifiedName) -> "android.support.annotation.Nullable"
+                        isNonNullAnnotation(qualifiedName) -> "android.support.annotation.NonNull"
+
+                    // Support library annotations are all included, as is the built-in stuff like @Retention
+                        qualifiedName.startsWith("android.support.annotation.") -> return qualifiedName
+                        qualifiedName.startsWith("java.lang.") -> return qualifiedName
+
+                    // Unknown Android platform annotations
+                        qualifiedName.startsWith("android.annotation.") -> {
+                            // Remove, unless specifically included in --showAnnotations
+                            return if (options.showAnnotations.contains(qualifiedName)) {
+                                qualifiedName
+                            } else {
+                                null
+                            }
+                        }
+
+                        else -> {
+                            // Remove, unless (a) public or (b) specifically included in --showAnnotations
+                            return if (options.showAnnotations.contains(qualifiedName)) {
+                                qualifiedName
+                            } else if (filter != null) {
+                                val cls = codebase.findClass(qualifiedName)
+                                if (cls != null && filter.test(cls)) {
+                                    qualifiedName
+                                } else {
+                                    null
+                                }
+                            } else {
+                                qualifiedName
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        /**
+         * Given a "full" annotation name, shortens it by removing redundant package names.
+         * This is intended to be used by the [Options.omitCommonPackages] flag
+         * to reduce clutter in signature files.
+         *
+         * For example, this method will convert `@android.support.annotation.Nullable` to just
+         * `@Nullable`, and `@android.support.annotation.IntRange(from=20)` to `IntRange(from=20)`.
+         */
+        fun shortenAnnotation(source: String): String {
+            return when {
+                source.startsWith("android.annotation.", 1) -> {
+                    "@" + source.substring("@android.annotation.".length)
+                }
+                source.startsWith("android.support.annotation.", 1) -> {
+                    "@" + source.substring("@android.support.annotation.".length)
+                }
+                else -> source
+            }
+        }
+
+        /**
+         * Reverses the [shortenAnnotation] method. Intended for use when reading in signature files
+         * that contain shortened type references.
+         */
+        fun unshortenAnnotation(source: String): String {
+            return when {
+            // These 3 annotations are in the android.annotation. package, not android.support.annotation
+                source.startsWith("@SystemService") ||
+                        source.startsWith("@TargetApi") ||
+                        source.startsWith("@SuppressLint") ->
+                    "@android.annotation." + source.substring(1)
+                else -> {
+                    "@android.support.annotation." + source.substring(1)
+                }
+            }
+        }
+    }
+}
+
+/** An attribute of an annotation, such as "value" */
+interface AnnotationAttribute {
+    /** The name of the annotation */
+    val name: String
+    /** The annotation value */
+    val value: AnnotationAttributeValue
+
+    /**
+     * Return all leaf values; this flattens the complication of handling
+     * {@code @SuppressLint("warning")} and {@code @SuppressLint({"warning1","warning2"})
+     */
+    fun leafValues(): List<AnnotationAttributeValue> {
+        val result = mutableListOf<AnnotationAttributeValue>()
+        AnnotationAttributeValue.addValues(value, result)
+        return result
+    }
+}
+
+/** An annotation value */
+interface AnnotationAttributeValue {
+    /** Generates source code for this annotation value */
+    fun toSource(): String
+
+    /** The value of the annotation */
+    fun value(): Any?
+
+    /** If the annotation declaration references a field (or class etc), return the resolved class */
+    fun resolve(): Item?
+
+    companion object {
+        fun addValues(value: AnnotationAttributeValue, into: MutableList<AnnotationAttributeValue>) {
+            if (value is AnnotationArrayAttributeValue) {
+                for (v in value.values) {
+                    addValues(v, into)
+                }
+            } else if (value is AnnotationSingleAttributeValue) {
+                into.add(value)
+            }
+        }
+    }
+}
+
+/** An annotation value (for a single item, not an array) */
+interface AnnotationSingleAttributeValue : AnnotationAttributeValue {
+    /** The annotation value, expressed as source code */
+    val valueSource: String
+    /** The annotation value */
+    val value: Any?
+
+    override fun value() = value
+}
+
+/** An annotation value for an array of items */
+interface AnnotationArrayAttributeValue : AnnotationAttributeValue {
+    /** The annotation values */
+    val values: List<AnnotationAttributeValue>
+
+    override fun resolve(): Item? {
+        error("resolve() should not be called on an array value")
+    }
+
+    override fun value() = values.mapNotNull { it.value() }.toTypedArray()
+}
+
+class DefaultAnnotationAttribute(
+    override val name: String,
+    override val value: DefaultAnnotationValue
+) : AnnotationAttribute {
+    companion object {
+        fun create(name: String, value: String): DefaultAnnotationAttribute {
+            return DefaultAnnotationAttribute(name, DefaultAnnotationValue.create(value))
+        }
+
+        fun createList(source: String): List<AnnotationAttribute> {
+            val list = mutableListOf<AnnotationAttribute>()
+            if (source.contains("{")) {
+                assert(source.indexOf('{', source.indexOf('{', source.indexOf('{') + 1) + 1) != -1,
+                    { "Multiple arrays not supported: $source" })
+                val split = source.indexOf('=')
+                val name: String
+                val value: String
+                if (split == -1) {
+                    name = "value"
+                    value = source.substring(source.indexOf('{'))
+                } else {
+                    name = source.substring(0, split).trim()
+                    value = source.substring(split + 1).trim()
+                }
+                list.add(DefaultAnnotationAttribute.create(name, value))
+                return list
+            }
+
+            source.split(",").forEach { declaration ->
+                val split = declaration.indexOf('=')
+                val name: String
+                val value: String
+                if (split == -1) {
+                    name = "value"
+                    value = declaration.trim()
+                } else {
+                    name = declaration.substring(0, split).trim()
+                    value = declaration.substring(split + 1).trim()
+                }
+                list.add(DefaultAnnotationAttribute.create(name, value))
+            }
+            return list
+        }
+    }
+}
+
+abstract class DefaultAnnotationValue : AnnotationAttributeValue {
+    companion object {
+        fun create(value: String): DefaultAnnotationValue {
+            return if (value.startsWith("{")) { // Array
+                DefaultAnnotationArrayAttributeValue(value)
+            } else {
+                DefaultAnnotationSingleAttributeValue(value)
+            }
+        }
+    }
+
+    override fun toString(): String = toSource()
+}
+
+class DefaultAnnotationSingleAttributeValue(override val valueSource: String) : DefaultAnnotationValue(),
+    AnnotationSingleAttributeValue {
+    @Suppress("IMPLICIT_CAST_TO_ANY")
+    override val value = when {
+        valueSource == SdkConstants.VALUE_TRUE -> true
+        valueSource == SdkConstants.VALUE_FALSE -> false
+        valueSource.startsWith("\"") -> valueSource.removeSurrounding("\"")
+        valueSource.startsWith('\'') -> valueSource.removeSurrounding("'")[0]
+        else -> try {
+            if (valueSource.contains(".")) {
+                valueSource.toDouble()
+            } else {
+                valueSource.toLong()
+            }
+        } catch (e: NumberFormatException) {
+            valueSource
+        }
+    }
+
+    override fun resolve(): Item? = null
+
+    override fun toSource() = valueSource
+}
+
+class DefaultAnnotationArrayAttributeValue(val value: String) : DefaultAnnotationValue(),
+    AnnotationArrayAttributeValue {
+    init {
+        assert(value.startsWith("{") && value.endsWith("}"), { value })
+    }
+
+    override val values = value.substring(1, value.length - 1).split(",").map {
+        DefaultAnnotationValue.create(it.trim())
+    }.toList()
+
+    override fun toSource() = value
+}
diff --git a/src/main/java/com/android/tools/metalava/model/ClassItem.kt b/src/main/java/com/android/tools/metalava/model/ClassItem.kt
new file mode 100644
index 0000000..18bdc7f
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/model/ClassItem.kt
@@ -0,0 +1,677 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava.model
+
+import com.android.tools.metalava.ApiAnalyzer
+import com.android.tools.metalava.compatibility
+import com.android.tools.metalava.model.visitors.ApiVisitor
+import com.android.tools.metalava.model.visitors.ItemVisitor
+import com.android.tools.metalava.model.visitors.TypeVisitor
+import com.android.tools.metalava.options
+import java.util.ArrayList
+import java.util.LinkedHashSet
+import java.util.function.Predicate
+
+interface ClassItem : Item {
+    /** The simple name of a class. In class foo.bar.Outer.Inner, the simple name is "Inner" */
+    fun simpleName(): String
+
+    /** The full name of a class. In class foo.bar.Outer.Inner, the full name is "Outer.Inner" */
+    fun fullName(): String
+
+    /** The qualified name of a class. In class foo.bar.Outer.Inner, the qualified name is the whole thing. */
+    fun qualifiedName(): String
+
+    /** Is this an innerclass? */
+    fun isInnerClass(): Boolean = containingClass() != null
+
+    /** Is this a top level class? */
+    fun isTopLevelClass(): Boolean = containingClass() == null
+
+    /** This [ClassItem] and all of its inner classes, recursively */
+    fun allClasses(): Sequence<ClassItem> {
+        return sequenceOf(this).plus(innerClasses().asSequence().flatMap { it.allClasses() })
+    }
+
+    override fun parent(): Item? = containingClass() ?: containingPackage()
+
+    /**
+     * The qualified name where inner classes use $ as a separator.
+     * In class foo.bar.Outer.Inner, this method will return foo.bar.Outer$Inner.
+     * (This is the name format used in ProGuard keep files for example.)
+     */
+    fun qualifiedNameWithDollarInnerClasses(): String {
+        var curr: ClassItem? = this
+        while (curr?.containingClass() != null) {
+            curr = curr.containingClass()
+        }
+
+        if (curr == null) {
+            return fullName().replace('.', '$')
+        }
+
+        return curr.containingPackage().qualifiedName() + "." + fullName().replace('.', '$')
+    }
+
+    /** Returns the internal name of the class, as seen in bytecode */
+    fun internalName(): String {
+        var curr: ClassItem? = this
+        while (curr?.containingClass() != null) {
+            curr = curr.containingClass()
+        }
+
+        if (curr == null) {
+            return fullName().replace('.', '$')
+        }
+
+        return curr.containingPackage().qualifiedName().replace('.', '/') + "/" +
+                fullName().replace('.', '$')
+    }
+
+    /** The super class of this class, if any  */
+    fun superClass(): ClassItem?
+
+    /** The super class type of this class, if any. The difference between this and [superClass] is
+     * that the type reference can include type arguments; e.g. in "class MyList extends List<String>"
+     * the super class is java.util.List and the super class type is java.util.List<java.lang.String>.
+     * */
+    fun superClassType(): TypeItem?
+
+    /** Finds the public super class of this class, if any */
+    fun publicSuperClass(): ClassItem? {
+        var superClass = superClass()
+        while (superClass != null && !superClass.checkLevel()) {
+            superClass = superClass.superClass()
+        }
+
+        return superClass
+    }
+
+    /** Any interfaces implemented by this class */
+    fun interfaceTypes(): List<TypeItem>
+
+    /** All classes and interfaces implemented (by this class and its super classes and the interfaces themselves) */
+    fun allInterfaces(): Sequence<ClassItem>
+
+    /** Any inner classes of this class */
+    fun innerClasses(): List<ClassItem>
+
+    /** The constructors in this class */
+    fun constructors(): List<ConstructorItem>
+
+    /** Whether this class has an implicit default constructor */
+    fun hasImplicitDefaultConstructor(): Boolean
+
+    /** The non-constructor methods in this class */
+    fun methods(): List<MethodItem>
+
+    /** The fields in this class */
+    fun fields(): List<FieldItem>
+
+    /** The members in this class: constructors, methods, fields/enum constants */
+    fun members(): Sequence<MemberItem> {
+        return fields().asSequence().plus(constructors().asSequence()).plus(methods().asSequence())
+    }
+
+    /** Whether this class is an interface */
+    fun isInterface(): Boolean
+
+    /** Whether this class is an annotation type */
+    fun isAnnotationType(): Boolean
+
+    /** Whether this class is an enum */
+    fun isEnum(): Boolean
+
+    /** Whether this class is an interface */
+    fun isClass(): Boolean = !isInterface() && !isAnnotationType() && !isEnum()
+
+    /** The containing class, for inner classes */
+    fun containingClass(): ClassItem?
+
+    /** The containing package */
+    fun containingPackage(): PackageItem
+
+    /** Gets the type for this class */
+    fun toType(): TypeItem
+
+    /** Returns true if this class has type parameters */
+    fun hasTypeVariables(): Boolean
+
+    /** Any type parameters for the class, if any, as a source string (with fully qualified class names) */
+    fun typeParameterList(): String?
+
+    fun typeParameterNames(): List<String>
+
+    /** Returns the classes that are part of the type parameters of this method, if any */
+    fun typeArgumentClasses(): List<ClassItem> = TODO("Not yet implemented")
+
+    fun isJavaLangObject(): Boolean {
+        return qualifiedName() == "java.lang.Object"
+    }
+
+    // Mutation APIs: Used to "fix up" the API hierarchy (in [ApiAnalyzer]) to only expose
+    // visible parts of the API)
+
+    // This replaces the "real" super class
+    fun setSuperClass(superClass: ClassItem?, superClassType: TypeItem? = superClass?.toType())
+
+    // This replaces the interface types implemented by this class
+    fun setInterfaceTypes(interfaceTypes: List<TypeItem>)
+
+    val isTypeParameter: Boolean
+
+    var hasPrivateConstructor: Boolean
+
+    override fun accept(visitor: ItemVisitor) {
+        if (visitor is ApiVisitor) {
+            accept(visitor)
+            return
+        }
+
+        if (visitor.skip(this)) {
+            return
+        }
+
+        visitor.visitItem(this)
+        visitor.visitClass(this)
+
+        for (constructor in constructors()) {
+            constructor.accept(visitor)
+        }
+
+        for (method in methods()) {
+            method.accept(visitor)
+        }
+
+        if (isEnum()) {
+            // In enums, visit the enum constants first, then the fields
+            for (field in fields()) {
+                if (field.isEnumConstant()) {
+                    field.accept(visitor)
+                }
+            }
+            for (field in fields()) {
+                if (!field.isEnumConstant()) {
+                    field.accept(visitor)
+                }
+            }
+        } else {
+            for (field in fields()) {
+                field.accept(visitor)
+            }
+        }
+
+        if (visitor.nestInnerClasses) {
+            for (cls in innerClasses()) {
+                cls.accept(visitor)
+            }
+        } // otherwise done below
+
+        visitor.afterVisitClass(this)
+        visitor.afterVisitItem(this)
+
+        if (!visitor.nestInnerClasses) {
+            for (cls in innerClasses()) {
+                cls.accept(visitor)
+            }
+        }
+    }
+
+    fun accept(visitor: ApiVisitor) {
+        if (visitor.skip(this)) {
+            return
+        }
+
+        if (!visitor.include(this)) {
+            return
+        }
+
+        // We build up a separate data structure such that we can compute the
+        // sets of fields, methods, etc even for inner classes (recursively); that way
+        // we can easily and up front determine whether we have any matches for
+        // inner classes (which is vital for computing the removed-api for example, where
+        // only something like the appearance of a removed method inside an inner class
+        // results in the outer class being described in the signature file.
+        val candidate = VisitCandidate(this, visitor)
+        candidate.accept()
+    }
+
+    override fun acceptTypes(visitor: TypeVisitor) {
+        if (visitor.skip(this)) {
+            return
+        }
+
+        val type = toType()
+        visitor.visitType(type, this)
+
+        // TODO: Visit type parameter list (at least the bounds types, e.g. View in <T extends View>
+        superClass()?.let {
+            visitor.visitType(it.toType(), it)
+        }
+
+        if (visitor.includeInterfaces) {
+            for (itf in interfaceTypes()) {
+                val owner = itf.asClass()
+                owner?.let { visitor.visitType(itf, it) }
+            }
+        }
+
+        for (constructor in constructors()) {
+            constructor.acceptTypes(visitor)
+        }
+        for (field in fields()) {
+            field.acceptTypes(visitor)
+        }
+        for (method in methods()) {
+            method.acceptTypes(visitor)
+        }
+        for (cls in innerClasses()) {
+            cls.acceptTypes(visitor)
+        }
+
+        visitor.afterVisitType(type, this)
+    }
+
+    companion object {
+        // Same as doclava1 (modulo the new handling when class names match
+        val comparator: Comparator<in ClassItem> = Comparator { o1, o2 ->
+            val delta = o1.fullName().compareTo(o2.fullName())
+            if (delta == 0) {
+                o1.qualifiedName().compareTo(o2.qualifiedName())
+            } else {
+                delta
+            }
+        }
+
+        val nameComparator: Comparator<ClassItem> = Comparator { a, b ->
+            a.simpleName().compareTo(b.simpleName())
+        }
+
+        val fullNameComparator: Comparator<ClassItem> = Comparator { a, b -> a.fullName().compareTo(b.fullName()) }
+
+        val qualifiedComparator: Comparator<ClassItem> = Comparator { a, b ->
+            a.qualifiedName().compareTo(b.qualifiedName())
+        }
+
+        fun classNameSorter(): Comparator<in ClassItem> =
+            if (compatibility.sortClassesBySimpleName) {
+                ClassItem.comparator
+            } else {
+                ClassItem.qualifiedComparator
+            }
+    }
+
+    fun findMethod(methodName: String, parameters: String): MethodItem?
+
+    fun findField(fieldName: String): FieldItem?
+
+    /** Returns the corresponding compilation unit, if any */
+    fun getCompilationUnit(): CompilationUnit? = null
+
+    /**
+     * Return superclass matching the given predicate. When a superclass doesn't
+     * match, we'll keep crawling up the tree until we find someone who matches.
+     */
+    fun filteredSuperclass(predicate: Predicate<Item>): ClassItem? {
+        val superClass = superClass() ?: return null
+        return if (predicate.test(superClass)) {
+            superClass
+        } else {
+            superClass.filteredSuperclass(predicate)
+        }
+    }
+
+    fun filteredSuperClassType(predicate: Predicate<Item>): TypeItem? {
+        var superClassType: TypeItem? = superClassType() ?: return null
+        var prev: ClassItem? = null
+        while (superClassType != null) {
+            val superClass = superClassType.asClass() ?: return null
+            if (predicate.test(superClass)) {
+                if (prev == null || superClass == superClass()) {
+                    // Direct reference; no need to map type variables
+                    return superClassType
+                }
+                if (!superClassType.hasTypeArguments()) {
+                    // No type variables - also no need for mapping
+                    return superClassType
+                }
+
+                return superClassType.convertType(this, prev)
+            }
+
+            prev = superClass
+            superClassType = superClass.superClassType()
+        }
+
+        return null
+    }
+
+    /**
+     * Return methods matching the given predicate. Forcibly includes local
+     * methods that override a matching method in an ancestor class.
+     */
+    fun filteredMethods(predicate: Predicate<Item>): Collection<MethodItem> {
+        val methods = LinkedHashSet<MethodItem>()
+        for (method in methods()) {
+            if (predicate.test(method) || method.findPredicateSuperMethod(predicate) != null) {
+                //val duplicated = method.duplicate(this)
+                //methods.add(duplicated)
+                methods.remove(method)
+                methods.add(method)
+            }
+        }
+        return methods
+    }
+
+    /** Returns the constructors that match the given predicate */
+    fun filteredConstructors(predicate: Predicate<Item>): Sequence<ConstructorItem> {
+        return constructors().asSequence().filter { predicate.test(it) }
+    }
+
+    /**
+     * Return fields matching the given predicate. Also clones fields from
+     * ancestors that would match had they been defined in this class.
+     */
+    fun filteredFields(predicate: Predicate<Item>): List<FieldItem> {
+        val fields = LinkedHashSet<FieldItem>()
+        if (options.showUnannotated) {
+            for (clazz in allInterfaces()) {
+                if (!clazz.isInterface()) {
+                    continue
+                }
+                for (field in clazz.fields()) {
+                    if (!predicate.test(field)) {
+                        val clz = this
+                        val duplicated = field.duplicate(clz)
+                        if (predicate.test(duplicated)) {
+                            fields.remove(duplicated)
+                            fields.add(duplicated)
+                        }
+                    }
+                }
+            }
+        }
+        for (field in fields()) {
+            if (predicate.test(field)) {
+                fields.remove(field)
+                fields.add(field)
+            }
+        }
+        if (fields.isEmpty()) {
+            return emptyList()
+        }
+        val list = fields.toMutableList()
+        list.sortWith(FieldItem.comparator)
+        return list
+    }
+
+    fun filteredInterfaceTypes(predicate: Predicate<Item>): Collection<TypeItem> {
+        val interfaceTypes = filteredInterfaceTypes(
+            predicate, LinkedHashSet(),
+            includeSelf = false, includeParents = false, target = this
+        )
+        if (interfaceTypes.isEmpty()) {
+            return interfaceTypes
+        }
+
+        return interfaceTypes
+    }
+
+    fun allInterfaceTypes(predicate: Predicate<Item>): Collection<TypeItem> {
+        val interfaceTypes = filteredInterfaceTypes(
+            predicate, LinkedHashSet(),
+            includeSelf = false, includeParents = true, target = this
+        )
+        if (interfaceTypes.isEmpty()) {
+            return interfaceTypes
+        }
+
+        return interfaceTypes
+    }
+
+    private fun filteredInterfaceTypes(
+        predicate: Predicate<Item>,
+        types: LinkedHashSet<TypeItem>,
+        includeSelf: Boolean,
+        includeParents: Boolean,
+        target: ClassItem
+    ): LinkedHashSet<TypeItem> {
+        val superClassType = superClassType()
+        if (superClassType != null) {
+            val superClass = superClassType.asClass()
+            if (superClass != null) {
+                if (!predicate.test(superClass)) {
+                    superClass.filteredInterfaceTypes(predicate, types, true, includeParents, target)
+                } else if (includeSelf && superClass.isInterface()) {
+                    types.add(superClassType)
+                    if (includeParents) {
+                        superClass.filteredInterfaceTypes(predicate, types, true, includeParents, target)
+                    }
+                }
+            }
+        }
+        for (type in interfaceTypes()) {
+            val cls = type.asClass() ?: continue
+            if (predicate.test(cls)) {
+                if (hasTypeVariables() && type.hasTypeArguments()) {
+                    val replacementMap = target.mapTypeVariables(this)
+                    if (replacementMap.isNotEmpty()) {
+                        val mapped = type.convertType(replacementMap)
+                        types.add(mapped)
+                        continue
+                    }
+                }
+                types.add(type)
+                if (includeParents) {
+                    cls.filteredInterfaceTypes(predicate, types, true, includeParents, target)
+                }
+            } else {
+                cls.filteredInterfaceTypes(predicate, types, true, includeParents, target)
+            }
+        }
+        return types
+    }
+
+    fun allInnerClasses(includeSelf: Boolean = false): Sequence<ClassItem> {
+        val list = ArrayList<ClassItem>()
+        if (includeSelf) {
+            list.add(this)
+        }
+        addInnerClasses(list, this)
+        return list.asSequence()
+    }
+
+    private fun addInnerClasses(list: MutableList<ClassItem>, cls: ClassItem) {
+        for (innerClass in cls.innerClasses()) {
+            list.add(innerClass)
+            addInnerClasses(list, innerClass)
+        }
+    }
+
+    /**
+     * The default constructor to invoke on this class from subclasses; initially null
+     * but populated by [ApiAnalyzer.addConstructors]. (Note that in some cases
+     * [defaultConstructor] may not be in [constructors], e.g. when we need to
+     * create a constructor to match a public parent class with a non-default constructor
+     * and the one in the code is not a match, e.g. is marked @hide etc.)
+     */
+    var defaultConstructor: ConstructorItem?
+
+    /**
+     * Creates a map of type variables from this class to the given target class.
+     * If class A<X,Y> extends B<X,Y>, and B is declared as class B<M,N>,
+     * this returns the map {"X"->"M", "Y"->"N"}. There could be multiple intermediate
+     * classes between this class and the target class, and in some cases we could
+     * be substituting in a concrete class, e.g. class MyClass extends B<String,Number>
+     * would return the map {"java.lang.String"->"M", "java.lang.Number"->"N"}.
+     *
+     * If [reverse] is true, compute the reverse map: keys are the variables in
+     * the target and the values are the variables in the source.
+     */
+    fun mapTypeVariables(target: ClassItem, reverse: Boolean = false): Map<String, String> = codebase.unsupported()
+
+    /**
+     * Creates a constructor in this class
+     */
+    fun createDefaultConstructor(): ConstructorItem = codebase.unsupported()
+
+    /**
+     * Creates a method corresponding to the given method signature in this class
+     */
+    fun createMethod(template: MethodItem): MethodItem = codebase.unsupported()
+
+    fun addMethod(method: MethodItem): Unit = codebase.unsupported()
+}
+
+class VisitCandidate(private val cls: ClassItem, private val visitor: ApiVisitor) {
+    private val innerClasses: Sequence<VisitCandidate>
+    private val constructors: Sequence<MethodItem>
+    private val methods: Sequence<MethodItem>
+    private val fields: Sequence<FieldItem>
+    private val enums: Sequence<FieldItem>
+
+    init {
+        val filterEmit = visitor.filterEmit
+
+        constructors = cls.constructors().asSequence()
+            .filter { filterEmit.test(it) }
+            .sortedWith(MethodItem.comparator)
+
+        methods = cls.methods().asSequence()
+            .filter { filterEmit.test(it) }
+            .sortedWith(MethodItem.comparator)
+
+        val fieldSequence =
+            if (visitor.inlineInheritedFields) {
+                cls.filteredFields(filterEmit).asSequence()
+            } else {
+                cls.fields().asSequence()
+            }
+        if (cls.isEnum()) {
+            fields = fieldSequence
+                .filter({ !it.isEnumConstant() })
+                .sortedWith(FieldItem.comparator)
+            enums = fieldSequence
+                .filter({ it.isEnumConstant() })
+                .filter { filterEmit.test(it) }
+                .sortedWith(FieldItem.comparator)
+        } else {
+            fields = fieldSequence.sortedWith(FieldItem.comparator)
+            enums = emptySequence()
+        }
+
+        innerClasses = cls.innerClasses()
+            .asSequence()
+            .sortedWith(ClassItem.classNameSorter())
+            .map { VisitCandidate(it, visitor) }
+    }
+
+    /** Will this class emit anything? */
+    private fun emit(): Boolean {
+        val emit = emitClass()
+        if (emit) {
+            return true
+        }
+
+        return innerClasses.any { it.emit() }
+    }
+
+    /** Does the body of this class (everything other than the inner classes) emit anything? */
+    private fun emitClass(): Boolean {
+        val classEmpty = (constructors.none() && methods.none() && enums.none() && fields.none())
+        return if (visitor.filterEmit.test(cls)) {
+            true
+        } else if (!classEmpty) {
+            visitor.filterReference.test(cls)
+        } else {
+            false
+        }
+    }
+
+    fun accept() {
+        if (visitor.skip(cls)) {
+            return
+        }
+
+        if (!visitor.include(cls)) {
+            return
+        }
+
+        if (!emit()) {
+            return
+        }
+
+        val emitClass = emitClass()
+
+        if (emitClass) {
+            if (!visitor.visitingPackage) {
+                visitor.visitingPackage = true
+                val pkg = cls.containingPackage()
+                visitor.visitItem(pkg)
+                visitor.visitPackage(pkg)
+            }
+
+            visitor.visitItem(cls)
+            visitor.visitClass(cls)
+
+            val sortedConstructors = if (visitor.methodComparator != null) {
+                constructors.sortedWith(visitor.methodComparator)
+            } else {
+                constructors
+            }
+            val sortedMethods = if (visitor.methodComparator != null) {
+                methods.sortedWith(visitor.methodComparator)
+            } else {
+                methods
+            }
+            val sortedFields = if (visitor.fieldComparator != null) {
+                fields.sortedWith(visitor.fieldComparator)
+            } else {
+                fields
+            }
+
+
+            for (constructor in sortedConstructors) {
+                constructor.accept(visitor)
+            }
+
+            for (method in sortedMethods) {
+                method.accept(visitor)
+            }
+
+            for (enumConstant in enums) {
+                enumConstant.accept(visitor)
+            }
+            for (field in sortedFields) {
+                field.accept(visitor)
+            }
+        }
+
+        if (visitor.nestInnerClasses) {  // otherwise done below
+            innerClasses.forEach { it.accept() }
+        }
+
+        if (emitClass) {
+            visitor.afterVisitClass(cls)
+            visitor.afterVisitItem(cls)
+        }
+
+        if (!visitor.nestInnerClasses) {
+            innerClasses.forEach { it.accept() }
+        }
+    }
+}
diff --git a/src/main/java/com/android/tools/metalava/model/Codebase.kt b/src/main/java/com/android/tools/metalava/model/Codebase.kt
new file mode 100644
index 0000000..3cb8d54
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/model/Codebase.kt
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava.model
+
+import com.android.SdkConstants.ANDROID_URI
+import com.android.SdkConstants.ATTR_NAME
+import com.android.SdkConstants.TAG_PERMISSION
+import com.android.tools.metalava.CodebaseComparator
+import com.android.tools.metalava.ComparisonVisitor
+import com.android.tools.metalava.doclava1.Errors
+import com.android.tools.metalava.model.text.TextBackedAnnotationItem
+import com.android.tools.metalava.model.visitors.ItemVisitor
+import com.android.tools.metalava.model.visitors.TypeVisitor
+import com.android.tools.metalava.reporter
+import com.android.utils.XmlUtils
+import com.android.utils.XmlUtils.getFirstSubTagByName
+import com.android.utils.XmlUtils.getNextTagByName
+import com.intellij.psi.PsiFile
+import org.intellij.lang.annotations.Language
+import java.io.File
+import java.util.function.Predicate
+import kotlin.text.Charsets.UTF_8
+
+/**
+ * Represents a complete unit of code -- typically in the form of a set
+ * of source trees, but also potentially backed by .jar files or even
+ * signature files
+ */
+interface Codebase {
+    /** Description of what this codebase is (useful during debugging) */
+    var description: String
+
+    /** The packages in the codebase (may include packages that are not included in the API) */
+    fun getPackages(): PackageList
+
+    /** The rough size of the codebase (package count) */
+    fun size(): Int
+
+    /** Returns a class identified by fully qualified name, if in the codebase */
+    fun findClass(className: String): ClassItem?
+
+    /** Returns a package identified by fully qualifiedname, if in the codebase */
+    fun findPackage(pkgName: String): PackageItem?
+
+    /** Returns true if this codebase supports documentation. */
+    fun supportsDocumentation(): Boolean
+
+    /**
+     * Returns true if this codebase corresponds to an already trusted API (e.g.
+     * is read in from something like an existing signature file); in that case,
+     * signature checks etc will not be performed.
+     */
+    fun trustedApi(): Boolean
+
+    fun accept(visitor: ItemVisitor) {
+        getPackages().accept(visitor)
+    }
+
+    fun acceptTypes(visitor: TypeVisitor) {
+        getPackages().acceptTypes(visitor)
+    }
+
+    /**
+     * Visits this codebase and compares it with another codebase, informing the visitors about
+     * the correlations and differences that it finds
+     */
+    fun compareWith(visitor: ComparisonVisitor, other: Codebase, filter: Predicate<Item>? = null) {
+        CodebaseComparator().compare(visitor, other, this, filter)
+    }
+
+    /**
+     * Creates an annotation item for the given (fully qualified) Java source
+     */
+    fun createAnnotation(
+        @Language("JAVA") source: String, context: Item? = null,
+        mapName: Boolean = true
+    ): AnnotationItem = TextBackedAnnotationItem(
+        this, source, mapName
+    )
+
+    /**
+     * Returns true if the codebase contains one or more Kotlin files
+     */
+    fun hasKotlin(): Boolean {
+        return units.any { it.fileType.name == "Kotlin" }
+    }
+
+    /**
+     * Returns true if the codebase contains one or more Java files
+     */
+    fun hasJava(): Boolean {
+        return units.any { it.fileType.name == "JAVA" }
+    }
+
+    /** The manifest to associate with this codebase, if any */
+    var manifest: File?
+
+    /**
+     * Returns the permission level of the named permission, if specified
+     * in the manifest. This method should only be called if the codebase has
+     * been configured with a manifest
+     */
+    fun getPermissionLevel(name: String): String?
+
+    /** Clear the [Item.tag] fields (prior to iteration like DFS) */
+    fun clearTags() {
+        getPackages().packages.forEach { pkg -> pkg.allClasses().forEach { cls -> cls.tag = false } }
+    }
+
+    /**
+     * Creates a filtered version of this codebase
+     */
+    fun filter(filterEmit: Predicate<Item>, filterReference: Predicate<Item>): Codebase
+
+    /** Reports that the given operation is unsupported for this codebase type */
+    fun unsupported(desc: String? = null): Nothing
+
+    /** Whether this codebase supports staged nullability (RecentlyNullable etc) */
+    var supportsStagedNullability: Boolean
+
+    /** If this codebase was filtered from another codebase, this points to the original */
+    var original: Codebase?
+
+    /** Returns the compilation units used in this codebase (may be empty
+     * when the codebase is not loaded from source, such as from .jar files or
+     * from signature files) */
+    var units: List<PsiFile>
+}
+
+abstract class DefaultCodebase : Codebase {
+    override var manifest: File? = null
+    private var permissions: Map<String, String>? = null
+    override var original: Codebase? = null
+    override var supportsStagedNullability: Boolean = false
+    override var units: List<PsiFile> = emptyList()
+
+    override fun getPermissionLevel(name: String): String? {
+        if (permissions == null) {
+            assert(manifest != null,
+                { "This method should only be called when a manifest has been configured on the codebase" })
+            try {
+                val map = HashMap<String, String>(600)
+                val doc = XmlUtils.parseDocument(manifest?.readText(UTF_8), true)
+                var current = getFirstSubTagByName(doc.documentElement, TAG_PERMISSION)
+                while (current != null) {
+                    val permissionName = current.getAttributeNS(ANDROID_URI, ATTR_NAME)
+                    val protectionLevel = current.getAttributeNS(ANDROID_URI, "protectionLevel")
+                    map.put(permissionName, protectionLevel)
+                    current = getNextTagByName(current, TAG_PERMISSION)
+                }
+                permissions = map
+            } catch (error: Throwable) {
+                reporter.report(Errors.PARSE_ERROR, manifest, "Failed to parse $manifest: ${error.message}")
+                permissions = emptyMap()
+            }
+        }
+
+        return permissions!![name]
+    }
+
+    override fun unsupported(desc: String?): Nothing {
+        error(desc ?: "This operation is not available on this type of codebase (${this.javaClass.simpleName})")
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/model/CompilationUnit.kt b/src/main/java/com/android/tools/metalava/model/CompilationUnit.kt
new file mode 100644
index 0000000..6f75493
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/model/CompilationUnit.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava.model
+
+import com.intellij.lang.Language
+import com.intellij.psi.PsiFile
+import java.util.function.Predicate
+
+/** Represents a compilation unit (e.g. a .java or a .kt file) */
+open class CompilationUnit(
+    val file: PsiFile
+) {
+
+    val language: Language? get() = file.language
+
+    open fun getHeaderComments(): String? = null
+
+    override fun toString(): String = "compilation unit ${file.virtualFile?.path}"
+
+    open fun getImportStatements(predicate: Predicate<Item>): Collection<String>? = null
+}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/model/ConstructorItem.kt b/src/main/java/com/android/tools/metalava/model/ConstructorItem.kt
new file mode 100644
index 0000000..68eb476
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/model/ConstructorItem.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava.model
+
+interface ConstructorItem : MethodItem {
+    override fun isConstructor(): Boolean = true
+
+    /** Returns the internal name of the class, as seen in bytecode */
+    override fun internalName(): String = "<init>"
+
+    /**
+     * The constructor that this method delegates to initially (e.g. super- or this- or default/implicit null
+     * constructor). Note that it may not be in a super class, as in the case of a this-call.
+     */
+    var superConstructor: ConstructorItem?
+}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/model/FieldItem.kt b/src/main/java/com/android/tools/metalava/model/FieldItem.kt
new file mode 100644
index 0000000..1dbd8d1
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/model/FieldItem.kt
@@ -0,0 +1,304 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava.model
+
+import com.android.tools.metalava.model.visitors.ItemVisitor
+import com.android.tools.metalava.model.visitors.TypeVisitor
+import java.io.PrintWriter
+
+interface FieldItem : MemberItem {
+    /** The type of this field */
+    fun type(): TypeItem
+
+    /**
+     * The initial/constant value, if any. If [requireConstant] the initial value will
+     * only be returned if it's constant.
+     */
+    fun initialValue(requireConstant: Boolean = true): Any?
+
+    /**
+     * An enum can contain both enum constants and fields; this method provides a way
+     * to distinguish between them.
+     */
+    fun isEnumConstant(): Boolean
+
+    /**
+     * Duplicates this field item. Used when we need to insert inherited fields from
+     * interfaces etc.
+     */
+    fun duplicate(targetContainingClass: ClassItem): FieldItem
+
+    override fun accept(visitor: ItemVisitor) {
+        if (visitor.skip(this)) {
+            return
+        }
+
+        visitor.visitItem(this)
+        visitor.visitField(this)
+
+        visitor.afterVisitField(this)
+        visitor.afterVisitItem(this)
+    }
+
+    override fun acceptTypes(visitor: TypeVisitor) {
+        if (visitor.skip(this)) {
+            return
+        }
+
+        val type = type()
+        visitor.visitType(type, this)
+        visitor.afterVisitType(type, this)
+    }
+
+    companion object {
+        val comparator: java.util.Comparator<FieldItem> = Comparator { a, b -> a.name().compareTo(b.name()) }
+    }
+
+    /**
+     * If this field has an initial value, it just writes ";", otherwise it writes
+     * " = value;" with the correct Java syntax for the initial value
+     */
+    fun writeValueWithSemicolon(
+        writer: PrintWriter,
+        allowDefaultValue: Boolean = false,
+        requireInitialValue: Boolean = false
+    ) {
+        val value =
+            initialValue(!allowDefaultValue)
+                    ?: if (allowDefaultValue && !containingClass().isClass()) type().defaultValue() else null
+        if (value != null) {
+            when (value) {
+                is Int -> {
+                    writer.print(" = ")
+                    writer.print(value)
+                    writer.print("; // 0x")
+                    writer.print(Integer.toHexString(value))
+                }
+                is String -> {
+                    writer.print(" = ")
+                    writer.print('"')
+                    writer.print(javaEscapeString(value))
+                    writer.print('"')
+                    writer.print(";")
+                }
+                is Long -> {
+                    writer.print(" = ")
+                    writer.print(value)
+                    writer.print(String.format("L; // 0x%xL", value))
+                }
+                is Boolean -> {
+                    writer.print(" = ")
+                    writer.print(value)
+                    writer.print(";")
+                }
+                is Byte -> {
+                    writer.print(" = ")
+                    writer.print(value)
+                    writer.print("; // 0x")
+                    writer.print(Integer.toHexString(value.toInt()))
+                }
+                is Short -> {
+                    writer.print(" = ")
+                    writer.print(value)
+                    writer.print("; // 0x")
+                    writer.print(Integer.toHexString(value.toInt()))
+                }
+                is Float -> {
+                    writer.print(" = ")
+                    when (value) {
+                        Float.POSITIVE_INFINITY -> writer.print("(1.0f/0.0f);")
+                        Float.NEGATIVE_INFINITY -> writer.print("(-1.0f/0.0f);")
+                        Float.NaN -> writer.print("(0.0f/0.0f);")
+                        else -> {
+                            writer.print(canonicalizeFloatingPointString(value.toString()))
+                            writer.print("f;")
+                        }
+                    }
+                }
+                is Double -> {
+                    writer.print(" = ")
+                    when (value) {
+                        Double.POSITIVE_INFINITY -> writer.print("(1.0/0.0);")
+                        Double.NEGATIVE_INFINITY -> writer.print("(-1.0/0.0);")
+                        Double.NaN -> writer.print("(0.0/0.0);")
+                        else -> {
+                            writer.print(canonicalizeFloatingPointString(value.toString()))
+                            writer.print(";")
+                        }
+                    }
+                }
+                is Char -> {
+                    writer.print(" = ")
+                    val intValue = value.toInt()
+                    writer.print(intValue)
+                    writer.print("; // ")
+                    writer.print(
+                        String.format(
+                            "0x%04x '%s'", intValue,
+                            javaEscapeString(value.toString())
+                        )
+                    )
+                }
+                else -> {
+                    writer.print(';')
+                }
+            }
+        } else {
+            // in interfaces etc we must have an initial value
+            if (requireInitialValue && !containingClass().isClass()) {
+                writer.print(" = null")
+            }
+            writer.print(';')
+        }
+    }
+}
+
+fun javaEscapeString(str: String): String {
+    var result = ""
+    val n = str.length
+    for (i in 0 until n) {
+        val c = str[i]
+        result += when (c) {
+            '\\' -> "\\\\"
+            '\t' -> "\\t"
+            '\b' -> "\\b"
+            '\r' -> "\\r"
+            '\n' -> "\\n"
+            '\'' -> "\\'"
+            '\"' -> "\\\""
+            in ' '..'~' -> c
+            else -> String.format("\\u%04x", c.toInt())
+        }
+    }
+    return result
+}
+
+// From doclava1 TextFieldItem#javaUnescapeString
+fun javaUnescapeString(str: String): String {
+    val n = str.length
+    var simple = true
+    for (i in 0 until n) {
+        val c = str[i]
+        if (c == '\\') {
+            simple = false
+            break
+        }
+    }
+    if (simple) {
+        return str
+    }
+
+    val buf = StringBuilder(str.length)
+    var escaped: Char = 0.toChar()
+    val START = 0
+    val CHAR1 = 1
+    val CHAR2 = 2
+    val CHAR3 = 3
+    val CHAR4 = 4
+    val ESCAPE = 5
+    var state = START
+
+    for (i in 0 until n) {
+        val c = str[i]
+        when (state) {
+            START -> if (c == '\\') {
+                state = ESCAPE
+            } else {
+                buf.append(c)
+            }
+            ESCAPE -> when (c) {
+                '\\' -> {
+                    buf.append('\\')
+                    state = START
+                }
+                't' -> {
+                    buf.append('\t')
+                    state = START
+                }
+                'b' -> {
+                    buf.append('\b')
+                    state = START
+                }
+                'r' -> {
+                    buf.append('\r')
+                    state = START
+                }
+                'n' -> {
+                    buf.append('\n')
+                    state = START
+                }
+                '\'' -> {
+                    buf.append('\'')
+                    state = START
+                }
+                '\"' -> {
+                    buf.append('\"')
+                    state = START
+                }
+                'u' -> {
+                    state = CHAR1
+                    escaped = 0.toChar()
+                }
+            }
+            CHAR1, CHAR2, CHAR3, CHAR4 -> {
+
+                escaped = (escaped.toInt() shl 4).toChar()
+                escaped = when (c) {
+                    in '0'..'9' -> (escaped.toInt() or (c - '0')).toChar()
+                    in 'a'..'f' -> (escaped.toInt() or (10 + (c - 'a'))).toChar()
+                    in 'A'..'F' -> (escaped.toInt() or (10 + (c - 'A'))).toChar()
+                    else -> throw IllegalArgumentException(
+                        "bad escape sequence: '" + c + "' at pos " + i + " in: \""
+                                + str + "\""
+                    )
+                }
+                if (state == CHAR4) {
+                    buf.append(escaped)
+                    state = START
+                } else {
+                    state++
+                }
+            }
+        }
+    }
+    if (state != START) {
+        throw IllegalArgumentException("unfinished escape sequence: " + str)
+    }
+    return buf.toString()
+}
+
+/**
+ * Returns a canonical string representation of a floating point
+ * number. The representation is suitable for use as Java source
+ * code. This method also addresses bug #4428022 in the Sun JDK.
+ */
+// From doclava1
+fun canonicalizeFloatingPointString(value: String): String {
+    var str = value
+    if (str.indexOf('E') != -1) {
+        return str
+    }
+
+    // 1.0 is the only case where a trailing "0" is allowed.
+    // 1.00 is canonicalized as 1.0.
+    var i = str.length - 1
+    val d = str.indexOf('.')
+    while (i >= d + 2 && str[i] == '0') {
+        str = str.substring(0, i--)
+    }
+    return str
+}
diff --git a/src/main/java/com/android/tools/metalava/model/Item.kt b/src/main/java/com/android/tools/metalava/model/Item.kt
new file mode 100644
index 0000000..7197729
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/model/Item.kt
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava.model
+
+import com.android.tools.metalava.model.visitors.ItemVisitor
+import com.android.tools.metalava.model.visitors.TypeVisitor
+import com.intellij.psi.PsiElement
+
+/**
+ * Represents a code element such as a package, a class, a method, a field, a parameter.
+ *
+ * This extra abstraction on top of PSI allows us to more model the API (and customize
+ * visibility, which cannot always be done by looking at a particular piece of code and examining
+ * visibility and @hide/@removed annotations: sometimes package private APIs are unhidden by
+ * being used in public APIs for example.
+ *
+ * The abstraction also lets us back the model by an alternative implementation read from
+ * signature files, to do compatibility checks.
+ * */
+interface Item {
+    val codebase: Codebase
+
+    /** Return the modifiers of this class */
+    val modifiers: ModifierList
+
+    /**
+     * Whether this element should be part of the API. The algorithm for this is complicated, so it can't
+     * be computed initially; we'll make passes over the source code to determine eligibility and mark all
+     * items as included or not.
+     */
+    var included: Boolean
+
+    /** Whether this element has been hidden with @hide/@Hide (or after propagation, in some containing class/pkg) */
+    var hidden: Boolean
+
+    var emit: Boolean
+
+    fun parent(): Item?
+
+    /** Recursive check to see if this item or any of its parents (containing class, containing package) are hidden */
+    fun hidden(): Boolean {
+        return hidden || parent()?.hidden() ?: false
+    }
+
+    /** Whether this element has been removed with @removed/@Remove (or after propagation, in some containing class) */
+    var removed: Boolean
+
+    /** True if this element has been marked deprecated */
+    val deprecated: Boolean
+
+    /** True if this element is only intended for documentation */
+    var docOnly: Boolean
+
+    /** True if this item is either hidden or removed */
+    fun isHiddenOrRemoved(): Boolean = hidden || removed
+
+    /** Visits this element using the given [visitor] */
+    fun accept(visitor: ItemVisitor)
+
+    /** Visits all types in this item hierarchy */
+    fun acceptTypes(visitor: TypeVisitor)
+
+    /** Get a mutable version of modifiers for this item */
+    fun mutableModifiers(): MutableModifierList
+
+    /** The javadoc/KDoc comment for this code element, if any. This is
+     * the original content of the documentation, including lexical tokens
+     * to begin, continue and end the comment (such as /+*).
+     * See [fullyQualifiedDocumentation] to look up the documentation with
+     * fully qualified references to classes.
+     */
+    var documentation: String
+
+    /** Looks up docs for a specific tag */
+    fun findTagDocumentation(tag: String): String?
+
+    /**
+     * A rank used for sorting. This allows signature files etc to
+     * sort similar items by a natural order, if non-zero.
+     * (Even though in signature files the elements are normally
+     * sorted first logically (constructors, then methods, then fields)
+     * and then alphabetically, this lets us preserve the source
+     * ordering for example for overloaded methods of the same name,
+     * where it's not clear that an alphabetical order (of each
+     * parameter?) would be preferable.)
+     */
+    val sortingRank: Int
+
+    /**
+     * Add the given text to the documentation.
+     *
+     * If the [tagSection] is null, add the comment to the initial text block
+     * of the description. Otherwise if it is "@return", add the comment
+     * to the return value. Otherwise the [tagSection] is taken to be the
+     * parameter name, and the comment added as parameter documentation
+     * for the given parameter.
+     */
+    fun appendDocumentation(comment: String, tagSection: String? = null, append: Boolean = true)
+
+    val isPublic: Boolean
+    val isProtected: Boolean
+    val isPackagePrivate: Boolean
+    val isPrivate: Boolean
+
+    // make sure these are implemented so we can place in maps:
+    override fun equals(other: Any?): Boolean
+
+    override fun hashCode(): Int
+
+    /**
+     * Returns true if this item requires nullness information (e.g. for a method
+     * where either the return value or any of the parameters are non-primitives.
+     * Note that it doesn't consider whether it already has nullness annotations;
+     * for that see [hasNullnessInfo].
+     */
+    fun requiresNullnessInfo(): Boolean {
+        return false
+    }
+
+    /**
+     * Whether this item was loaded from the classpath (e.g. jar dependencies)
+     * rather than be declared as source
+     */
+    fun isFromClassPath(): Boolean = false
+
+    /**
+     * Returns true if this item requires nullness information and supplies it
+     * (for all items, e.g. if a method is partially annotated this method would
+     * still return false)
+     */
+    fun hasNullnessInfo(): Boolean {
+        when (this) {
+            is ParameterItem -> {
+                return !type().primitive
+            }
+
+            is MethodItem -> {
+                val returnType = returnType()
+                if (returnType != null && !returnType.primitive) {
+                    return true
+                }
+                for (parameter in parameters()) {
+                    if (!parameter.type().primitive) {
+                        return true
+                    }
+                }
+                return false
+            }
+        }
+
+        return false
+    }
+
+    fun hasShowAnnotation(): Boolean = modifiers.hasShowAnnotation()
+    fun hasHideAnnotation(): Boolean = modifiers.hasHideAnnotations()
+
+    // TODO: Cache?
+    fun checkLevel(): Boolean {
+        if (isHiddenOrRemoved()) {
+            return false
+        }
+        return modifiers.isPublic() || modifiers.isProtected()
+    }
+
+    fun compilationUnit(): CompilationUnit? {
+        var curr: Item? = this
+        while (curr != null) {
+            if (curr is ClassItem && curr.isTopLevelClass()) {
+                return curr.getCompilationUnit()
+            }
+            curr = curr.parent()
+        }
+
+        return null
+    }
+
+    /** Returns the PSI element for this item, if any */
+    fun psi(): PsiElement? = null
+
+    /** Tag field used for DFS etc */
+    var tag: Boolean
+
+    /**
+     * Returns the [documentation], but with fully qualified links (except for the same package, and
+     * when turning a relative reference into a fully qualified reference, use the javadoc syntax
+     * for continuing to display the relative text, e.g. instead of {@link java.util.List}, use
+     * {@link java.util.List List}.
+     */
+    fun fullyQualifiedDocumentation(): String = documentation
+}
+
+abstract class DefaultItem(override val sortingRank: Int = nextRank++) : Item {
+    override val isPublic: Boolean get() = modifiers.isPublic()
+    override val isProtected: Boolean get() = modifiers.isProtected()
+    override val isPackagePrivate: Boolean get() = modifiers.isPackagePrivate()
+    override val isPrivate: Boolean get() = modifiers.isPrivate()
+
+    override var emit = true
+    override var tag: Boolean = false
+
+    // TODO: Get rid of this; with the new predicate approach it's redundant (and
+    // storing it per element is problematic since the predicate sometimes includes
+    // methods from parent interfaces etc)
+    override var included: Boolean = true
+
+    companion object {
+        private var nextRank: Int = 1
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/model/MemberItem.kt b/src/main/java/com/android/tools/metalava/model/MemberItem.kt
new file mode 100644
index 0000000..ecf9d57
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/model/MemberItem.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava.model
+
+interface MemberItem : Item {
+    /** The name of this method/field. Constructors have the same name as their containing class' simple name */
+    fun name(): String
+
+    /** Returns the internal name of the method, as seen in bytecode */
+    fun internalName(): String = name()
+
+    /** The containing class */
+    fun containingClass(): ClassItem
+
+    override fun parent(): ClassItem? = containingClass()
+}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/model/MethodItem.kt b/src/main/java/com/android/tools/metalava/model/MethodItem.kt
new file mode 100644
index 0000000..2e5823d
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/model/MethodItem.kt
@@ -0,0 +1,383 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava.model
+
+import com.android.tools.metalava.model.visitors.ItemVisitor
+import com.android.tools.metalava.model.visitors.TypeVisitor
+import java.util.LinkedHashSet
+import java.util.function.Predicate
+
+interface MethodItem : MemberItem {
+    /** Whether this method is a constructor */
+    fun isConstructor(): Boolean
+
+    /** The type of this field, or null for constructors */
+    fun returnType(): TypeItem?
+
+    /** The list of parameters */
+    fun parameters(): List<ParameterItem>
+
+    /** Returns the super methods that this method is overriding */
+    fun superMethods(): List<MethodItem>
+
+    fun allSuperMethods(): Sequence<MethodItem> {
+        val original = superMethods().firstOrNull() ?: return emptySequence()
+        return generateSequence(original) { item ->
+            val superMethods = item.superMethods()
+            superMethods.firstOrNull()
+        }
+    }
+
+    /** Any type parameters for the class, if any, as a source string (with fully qualified class names) */
+    fun typeParameterList(): String?
+
+    /** Returns the classes that are part of the type parameters of this method, if any */
+    fun typeArgumentClasses(): List<ClassItem> = TODO("Not yet implemented")
+
+    /** Types of exceptions that this method can throw */
+    fun throwsTypes(): List<ClassItem>
+
+    fun filteredThrowsTypes(predicate: Predicate<Item>): Collection<ClassItem> {
+        return filteredThrowsTypes(predicate, LinkedHashSet())
+    }
+
+    private fun filteredThrowsTypes(
+        predicate: Predicate<Item>,
+        classes: LinkedHashSet<ClassItem>
+    ): LinkedHashSet<ClassItem> {
+
+        for (cls in throwsTypes()) {
+            if (predicate.test(cls)) {
+                classes.add(cls)
+            } else {
+                // Excluded, but it may have super class throwables that are included; if so, include those
+                var curr = cls.publicSuperClass()
+                while (curr != null) {
+                    if (predicate.test(cls)) {
+                        classes.add(curr)
+                        break
+                    }
+                    curr = curr.publicSuperClass()
+                }
+            }
+        }
+        return classes
+    }
+
+    /**
+     * If this method is inherited from a hidden super class, but implements a method
+     * from a public interface, this property is set. This is necessary because these
+     * methods should not be listed in signature files (at least not in compatibility mode),
+     * whereas in stub files it's necessary for them to be included (otherwise subclasses
+     * may think the method required and not yet implemented, e.g. the class must be
+     * abstract.)
+     */
+    var inheritedInterfaceMethod: Boolean
+
+    /**
+     * Duplicates this field item. Used when we need to insert inherited fields from
+     * interfaces etc.
+     */
+    fun duplicate(targetContainingClass: ClassItem): MethodItem
+
+    fun findPredicateSuperMethod(predicate: Predicate<Item>): MethodItem? {
+        if (isConstructor()) {
+            return null
+        }
+
+        val superMethods = superMethods()
+        for (method in superMethods) {
+            if (predicate.test(method)) {
+                return method
+            }
+        }
+
+        for (method in superMethods) {
+            val found = method.findPredicateSuperMethod(predicate)
+            if (found != null) {
+                return found
+            }
+        }
+
+        return null
+    }
+
+    override fun accept(visitor: ItemVisitor) {
+        if (visitor.skip(this)) {
+            return
+        }
+
+        visitor.visitItem(this)
+        if (isConstructor()) {
+            visitor.visitConstructor(this as ConstructorItem)
+        } else {
+            visitor.visitMethod(this)
+        }
+
+        for (parameter in parameters()) {
+            parameter.accept(visitor)
+        }
+
+        if (isConstructor()) {
+            visitor.afterVisitConstructor(this as ConstructorItem)
+        } else {
+            visitor.afterVisitMethod(this)
+        }
+        visitor.afterVisitItem(this)
+    }
+
+    override fun acceptTypes(visitor: TypeVisitor) {
+        if (visitor.skip(this)) {
+            return
+        }
+
+        if (!isConstructor()) {
+            val type = returnType()
+            if (type != null) { // always true when not a constructor
+                visitor.visitType(type, this)
+            }
+        }
+
+        for (parameter in parameters()) {
+            parameter.acceptTypes(visitor)
+        }
+
+        for (exception in throwsTypes()) {
+            exception.acceptTypes(visitor)
+        }
+
+        if (!isConstructor()) {
+            val type = returnType()
+            if (type != null) {
+                visitor.visitType(type, this)
+            }
+        }
+    }
+
+    companion object {
+        private fun compareMethods(o1: MethodItem, o2: MethodItem): Int {
+            val name1 = o1.name()
+            val name2 = o2.name()
+            if (name1 == name2) {
+                val rankDelta = o1.sortingRank - o2.sortingRank
+                if (rankDelta != 0) {
+                    return rankDelta
+                }
+
+                // Compare by the rest of the signature to ensure stable output (we don't need to sort
+                // by return value or modifiers or modifiers or throws-lists since methods can't be overloaded
+                // by just those attributes
+                val p1 = o1.parameters()
+                val p2 = o2.parameters()
+                val p1n = p1.size
+                val p2n = p2.size
+                for (i in 0 until minOf(p1n, p2n)) {
+                    val compareTypes =
+                        p1[i].type().toTypeString().compareTo(p2[i].type().toTypeString(), ignoreCase = true)
+                    if (compareTypes != 0) {
+                        return compareTypes
+                    }
+                    // (Don't compare names; they're not part of the signatures)
+                }
+                return p1n.compareTo(p2n)
+            }
+
+            return name1.compareTo(name2)
+        }
+
+        val comparator: Comparator<MethodItem> = Comparator { o1, o2 -> compareMethods(o1, o2) }
+        val sourceOrderComparator: Comparator<MethodItem> = Comparator { o1, o2 ->
+            val delta = o1.sortingRank - o2.sortingRank
+            if (delta == 0) {
+                // Within a source file all the items will have unique sorting ranks, but since
+                // we copy methods in from hidden super classes it's possible for ranks to clash,
+                // and in that case we'll revert to a signature based comparison
+                comparator.compare(o1, o2)
+            } else {
+                delta
+            }
+        }
+
+        /** Gets the primary super method from a given method */
+        fun getPrimarySuperMethod(method: MethodItem): MethodItem? {
+            val superMethods = method.superMethods()
+            return when {
+                superMethods.isEmpty() -> null
+                superMethods.size > 1 -> {
+                    // Prefer default methods (or super class method bodies)
+                    superMethods
+                        .filter { it.modifiers.isDefault() || it.containingClass().isClass() }
+                        .forEach { return it }
+                    superMethods[0]
+                }
+                else -> superMethods[0]
+            }
+        }
+
+        fun sameSignature(method: MethodItem, superMethod: MethodItem, compareRawTypes: Boolean = false): Boolean {
+            // If the return types differ, override it (e.g. parent implements clone(),
+            // subclass overrides with more specific return type)
+            if (method.returnType() != superMethod.returnType()) {
+                return false
+            }
+
+            // IntentService#onStart - is it here because they vary in deprecation status?
+            if (method.deprecated != superMethod.deprecated) {
+                return false
+            }
+
+            // Compare modifier lists; note that here we need to
+            // skip modifiers that don't apply in compat mode if set
+            if (!method.modifiers.equivalentTo(superMethod.modifiers)) {
+                return false
+            }
+
+            val parameterList1 = method.parameters()
+            val parameterList2 = superMethod.parameters()
+
+            if (parameterList1.size != parameterList2.size) {
+                return false
+            }
+
+            assert(parameterList1.size == parameterList2.size)
+            for (i in 0 until parameterList1.size) {
+                val p1 = parameterList1[i]
+                val p2 = parameterList2[i]
+                val pt1 = p1.type()
+                val pt2 = p2.type()
+
+                if (compareRawTypes) {
+                    if (pt1.toErasedTypeString() != pt2.toErasedTypeString()) {
+                        return false
+                    }
+
+                } else {
+                    if (pt1 != pt2) {
+                        return false
+                    }
+                }
+
+                // TODO: Compare annotations to see for example whether
+                // you've refined the nullness policy; if so, that should be included
+            }
+
+            // Also compare throws lists
+            val throwsList12 = method.throwsTypes()
+            val throwsList2 = superMethod.throwsTypes()
+
+            if (throwsList12.size != throwsList2.size) {
+                return false
+            }
+
+            assert(throwsList12.size == throwsList2.size)
+            for (i in 0 until throwsList12.size) {
+                val p1 = throwsList12[i]
+                val p2 = throwsList2[i]
+                val pt1 = p1.qualifiedName()
+                val pt2 = p2.qualifiedName()
+                if (pt1 != pt2) { // assumes throws lists are sorted!
+                    return false
+                }
+            }
+
+            return true
+        }
+    }
+
+    fun formatParameters(): String? {
+        // TODO: Generalize, allow callers to control whether to include annotations, whether to erase types,
+        // whether to include names, etc
+        if (parameters().isEmpty()) {
+            return ""
+        }
+        val sb = StringBuilder()
+        for (parameter in parameters()) {
+            if (!sb.isEmpty()) {
+                sb.append(", ")
+            }
+            sb.append(parameter.type().toTypeString())
+        }
+
+        return sb.toString()
+    }
+
+    override fun requiresNullnessInfo(): Boolean {
+        if (isConstructor()) {
+            return false
+        } else if (returnType()?.primitive != true) {
+            return true
+        }
+        for (parameter in parameters()) {
+            if (!parameter.type().primitive) {
+                return true
+            }
+        }
+        return false
+    }
+
+    override fun hasNullnessInfo(): Boolean {
+        if (!isConstructor() && returnType()?.primitive != true) {
+            if (!modifiers.hasNullnessInfo()) {
+                return false
+            }
+        }
+
+        @Suppress("LoopToCallChain") // The quickfix is wrong! (covered by AnnotationStatisticsTest)
+        for (parameter in parameters()) {
+            if (!parameter.hasNullnessInfo()) {
+                return false
+            }
+        }
+
+        return true
+    }
+
+    fun isImplicitConstructor(): Boolean {
+        return isConstructor() && modifiers.isPublic() && parameters().isEmpty()
+    }
+
+    /**
+     * Returns true if this method is a signature match for the given method (e.g. can
+     * be overriding). This checks that the name and parameter lists match, but ignores
+     * differences in parameter names, return value types and throws list types.
+     */
+    fun matches(other: MethodItem): Boolean {
+        if (this === other) return true
+
+        if (name() != other.name()) {
+            return false
+        }
+
+        val parameters1 = parameters()
+        val parameters2 = other.parameters()
+
+        if (parameters1.size != parameters2.size) {
+            return false
+        }
+
+        for (i in 0 until parameters1.size) {
+            val parameter1 = parameters1[i]
+            val parameter2 = parameters2[i]
+            val type1 = parameter1.type().toErasedTypeString()
+            val type2 = parameter2.type().toErasedTypeString()
+            if (type1 != type2) {
+                return false
+            }
+        }
+        return true
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/model/ModifierList.kt b/src/main/java/com/android/tools/metalava/model/ModifierList.kt
new file mode 100644
index 0000000..5307ebe
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/model/ModifierList.kt
@@ -0,0 +1,294 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava.model
+
+import com.android.tools.metalava.Options
+import com.android.tools.metalava.compatibility
+import com.android.tools.metalava.options
+import java.io.Writer
+
+interface ModifierList {
+    val codebase: Codebase
+    fun annotations(): List<AnnotationItem>
+
+    fun owner(): Item
+    fun isPublic(): Boolean
+    fun isProtected(): Boolean
+    fun isPrivate(): Boolean
+    fun isStatic(): Boolean
+    fun isAbstract(): Boolean
+    fun isFinal(): Boolean
+    fun isNative(): Boolean
+    fun isSynchronized(): Boolean
+    fun isStrictFp(): Boolean
+    fun isTransient(): Boolean
+    fun isVolatile(): Boolean
+    fun isDefault(): Boolean
+
+    fun isEmpty(): Boolean
+
+    fun isPackagePrivate() = !(isPublic() || isProtected() || isPrivate())
+
+    // Rename? It's not a full equality, it's whether an override's modifier set is significant
+    fun equivalentTo(other: ModifierList): Boolean {
+        if (isPublic() != other.isPublic()) return false
+        if (isProtected() != other.isProtected()) return false
+        if (isPrivate() != other.isPrivate()) return false
+
+        if (isStatic() != other.isStatic()) return false
+        if (isAbstract() != other.isAbstract()) return false
+        if (isFinal() != other.isFinal()) return false
+        if (!compatibility.skipNativeModifier && isNative() != other.isNative()) return false
+        if (isSynchronized() != other.isSynchronized()) return false
+        if (!compatibility.skipStrictFpModifier && isStrictFp() != other.isStrictFp()) return false
+        if (isTransient() != other.isTransient()) return false
+        if (isVolatile() != other.isVolatile()) return false
+
+        // Default does not require an override to "remove" it
+        //if (isDefault() != other.isDefault()) return false
+
+        return true
+    }
+
+    /** Returns true if this modifier list contains any nullness information */
+    fun hasNullnessInfo(): Boolean {
+        return annotations().any { it.isNonNull() || it.isNullable() }
+    }
+
+    /**
+     * Returns true if this modifier list contains any annotations explicitly passed in
+     * via [Options.showAnnotations]
+     */
+    fun hasShowAnnotation(): Boolean {
+        if (options.showAnnotations.isEmpty()) {
+            return false
+        }
+        return annotations().any {
+            options.showAnnotations.contains(it.qualifiedName())
+        }
+    }
+
+    /**
+     * Returns true if this modifier list contains any annotations explicitly passed in
+     * via [Options.hideAnnotations]
+     */
+    fun hasHideAnnotations(): Boolean {
+        if (options.hideAnnotations.isEmpty()) {
+            return false
+        }
+        return annotations().any {
+            options.hideAnnotations.contains(it.qualifiedName())
+        }
+    }
+
+    /** Returns true if this modifier list contains the given annotation */
+    fun isAnnotatedWith(qualifiedName: String): Boolean {
+        return findAnnotation(qualifiedName) != null
+    }
+
+    /** Returns the annotation of the given qualified name if found in this modifier list */
+    fun findAnnotation(qualifiedName: String): AnnotationItem? {
+        val mappedName = AnnotationItem.mapName(codebase, qualifiedName)
+        return annotations().firstOrNull {
+            mappedName == it.qualifiedName()
+        }
+    }
+
+    companion object {
+        fun write(
+            writer: Writer,
+            modifiers: ModifierList,
+            item: Item,
+            // TODO: "deprecated" isn't a modifier; clarify method name
+            includeDeprecated: Boolean = false,
+            includeAnnotations: Boolean = true,
+            skipNullnessAnnotations: Boolean = false,
+            omitCommonPackages: Boolean = false,
+            removeAbstract: Boolean = false,
+            removeFinal: Boolean = false,
+            addPublic: Boolean = false
+        ) {
+
+            val list = if (removeAbstract || removeFinal || addPublic) {
+                class AbstractFiltering : ModifierList by modifiers {
+                    override fun isAbstract(): Boolean {
+                        return if (removeAbstract) false else modifiers.isAbstract()
+                    }
+
+                    override fun isFinal(): Boolean {
+                        return if (removeFinal) false else modifiers.isFinal()
+                    }
+
+                    override fun isPublic(): Boolean {
+                        return if (addPublic) true else modifiers.isPublic()
+                    }
+                }
+                AbstractFiltering()
+            } else {
+                modifiers
+            }
+
+            if (includeAnnotations && list.annotations().isNotEmpty()) {
+                for (annotation in list.annotations()) {
+                    if ((annotation.isNonNull() || annotation.isNullable())) {
+                        if (skipNullnessAnnotations) {
+                            continue
+                        }
+                    } else if (!annotation.isSignificant()) {
+                        continue
+                    }
+                    val source = annotation.toSource()
+                    if (omitCommonPackages) {
+                        writer.write(AnnotationItem.shortenAnnotation(source))
+                    } else {
+                        writer.write(source)
+                    }
+                    writer.write(" ")
+                }
+            }
+
+            // Abstract: should appear in interfaces if in compat mode
+            val classItem = item as? ClassItem
+            val methodItem = item as? MethodItem
+
+            // Order based on the old stubs code: TODO, use Java standard order instead?
+
+            if (compatibility.nonstandardModifierOrder) {
+                when {
+                    list.isPublic() -> writer.write("public ")
+                    list.isProtected() -> writer.write("protected ")
+                    list.isPrivate() -> writer.write("private ")
+                }
+
+                if (list.isDefault()) {
+                    writer.write("default ")
+                }
+
+                if (list.isStatic()) {
+                    writer.write("static ")
+                }
+
+                if (list.isFinal() &&
+                    // Don't show final on parameters: that's an implementation side detail
+                    item !is ParameterItem &&
+                    (classItem?.isEnum() != true || compatibility.finalInInterfaces) ||
+                    compatibility.forceFinalInEnumValueMethods &&
+                    methodItem?.name() == "values" && methodItem.containingClass().isEnum()
+                ) {
+                    writer.write("final ")
+                }
+
+                val isInterface = classItem?.isInterface() == true
+                        || (methodItem?.containingClass()?.isInterface() == true &&
+                        !list.isDefault() && !list.isStatic())
+
+                if ((compatibility.abstractInInterfaces && isInterface
+                            || list.isAbstract() &&
+                            (classItem?.isEnum() != true &&
+                                    (compatibility.abstractInAnnotations || classItem?.isAnnotationType() != true)))
+                    && (!isInterface || compatibility.abstractInInterfaces)
+                ) {
+                    writer.write("abstract ")
+                }
+
+                if (!compatibility.skipNativeModifier && list.isNative()) {
+                    writer.write("native ")
+                }
+
+                if (item.deprecated && includeDeprecated) {
+                    writer.write("deprecated ")
+                }
+
+                if (list.isSynchronized()) {
+                    writer.write("synchronized ")
+                }
+
+                if (!compatibility.skipStrictFpModifier && list.isStrictFp()) {
+                    writer.write("strictfp ")
+                }
+
+                if (list.isTransient()) {
+                    writer.write("transient ")
+                }
+
+                if (list.isVolatile()) {
+                    writer.write("volatile ")
+                }
+            } else {
+                if (item.deprecated && includeDeprecated) {
+                    writer.write("deprecated ")
+                }
+
+                when {
+                    list.isPublic() -> writer.write("public ")
+                    list.isProtected() -> writer.write("protected ")
+                    list.isPrivate() -> writer.write("private ")
+                }
+
+                val isInterface = classItem?.isInterface() == true
+                        || (methodItem?.containingClass()?.isInterface() == true &&
+                        !list.isDefault() && !list.isStatic())
+
+                if ((compatibility.abstractInInterfaces && isInterface
+                            || list.isAbstract() &&
+                            (classItem?.isEnum() != true &&
+                                    (compatibility.abstractInAnnotations || classItem?.isAnnotationType() != true)))
+                    && (!isInterface || compatibility.abstractInInterfaces)
+                ) {
+                    writer.write("abstract ")
+                }
+
+                if (list.isDefault() && item !is ParameterItem) {
+                    writer.write("default ")
+                }
+
+                if (list.isStatic()) {
+                    writer.write("static ")
+                }
+
+                if (list.isFinal() &&
+                    // Don't show final on parameters: that's an implementation side detail
+                    item !is ParameterItem &&
+                    (classItem?.isEnum() != true || compatibility.finalInInterfaces)
+                ) {
+                    writer.write("final ")
+                }
+
+                if (list.isTransient()) {
+                    writer.write("transient ")
+                }
+
+                if (list.isVolatile()) {
+                    writer.write("volatile ")
+                }
+
+                if (list.isSynchronized()) {
+                    writer.write("synchronized ")
+                }
+
+                if (!compatibility.skipNativeModifier && list.isNative()) {
+                    writer.write("native ")
+                }
+
+                if (!compatibility.skipStrictFpModifier && list.isStrictFp()) {
+                    writer.write("strictfp ")
+                }
+            }
+        }
+    }
+}
+
diff --git a/src/main/java/com/android/tools/metalava/model/MutableModifierList.kt b/src/main/java/com/android/tools/metalava/model/MutableModifierList.kt
new file mode 100644
index 0000000..feeb615
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/model/MutableModifierList.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava.model
+
+interface MutableModifierList : ModifierList {
+    fun setPublic(public: Boolean)
+    fun setProtected(protected: Boolean)
+    fun setPrivate(private: Boolean)
+    fun setStatic(static: Boolean)
+    fun setAbstract(abstract: Boolean)
+    fun setFinal(final: Boolean)
+    fun setNative(native: Boolean)
+    fun setSynchronized(synchronized: Boolean)
+    fun setStrictFp(strictfp: Boolean)
+    fun setTransient(transient: Boolean)
+    fun setVolatile(volatile: Boolean)
+    fun setDefault(default: Boolean)
+
+    fun addAnnotation(annotation: AnnotationItem)
+    fun removeAnnotation(annotation: AnnotationItem)
+    fun clearAnnotations(annotation: AnnotationItem)
+
+    fun setPackagePrivate(private: Boolean) {
+        setPublic(false)
+        setProtected(false)
+        setPrivate(false)
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/model/PackageItem.kt b/src/main/java/com/android/tools/metalava/model/PackageItem.kt
new file mode 100644
index 0000000..ed6e31c
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/model/PackageItem.kt
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava.model
+
+import com.android.tools.metalava.model.visitors.ApiVisitor
+import com.android.tools.metalava.model.visitors.ItemVisitor
+import com.android.tools.metalava.model.visitors.TypeVisitor
+import com.android.tools.metalava.tick
+
+interface PackageItem : Item {
+    /** The qualified name of this package */
+    fun qualifiedName(): String
+
+    /** All top level classes in this package */
+    fun topLevelClasses(): Sequence<ClassItem>
+
+    /** All top level classes **and inner classes** in this package */
+    fun allClasses(): Sequence<ClassItem> {
+        return topLevelClasses().asSequence().flatMap { it.allClasses() }
+    }
+
+    val isDefault get() = qualifiedName().isEmpty()
+
+    override fun parent(): PackageItem? = if (qualifiedName().isEmpty()) null else containingPackage()
+
+    fun containingPackage(): PackageItem? {
+        val name = qualifiedName()
+        val lastDot = name.lastIndexOf('.')
+        return if (lastDot != -1) {
+            codebase.findPackage(name.substring(0, lastDot))
+        } else {
+            null
+        }
+    }
+
+    /** Whether this package is empty */
+    fun empty() = topLevelClasses().none()
+
+    override fun accept(visitor: ItemVisitor) {
+        if (visitor.skipEmptyPackages && empty()) {
+            return
+        }
+
+        if (visitor is ApiVisitor) {
+            if (!emit) {
+                return
+            }
+
+            // For the API visitor packages are visited lazily; only when we encounter
+            // an unfiltered item within the class
+            topLevelClasses()
+                .asSequence()
+                .sortedWith(ClassItem.classNameSorter())
+                .forEach {
+                    tick()
+                    it.accept(visitor)
+                }
+
+            if (visitor.visitingPackage) {
+                visitor.visitingPackage = false
+                visitor.afterVisitPackage(this)
+                visitor.afterVisitItem(this)
+            }
+
+            return
+        }
+
+
+        if (visitor.skip(this)) {
+            return
+        }
+
+        visitor.visitItem(this)
+        visitor.visitPackage(this)
+
+        for (cls in topLevelClasses()) {
+            cls.accept(visitor)
+        }
+
+        visitor.afterVisitPackage(this)
+        visitor.afterVisitItem(this)
+    }
+
+    override fun acceptTypes(visitor: TypeVisitor) {
+        if (visitor.skip(this)) {
+            return
+        }
+
+        for (unit in topLevelClasses()) {
+            unit.acceptTypes(visitor)
+        }
+    }
+
+    companion object {
+        val comparator: Comparator<PackageItem> = Comparator { a, b -> a.qualifiedName().compareTo(b.qualifiedName()) }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/model/PackageList.kt b/src/main/java/com/android/tools/metalava/model/PackageList.kt
new file mode 100644
index 0000000..58ebf3c
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/model/PackageList.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava.model
+
+import com.android.tools.metalava.model.visitors.ItemVisitor
+import com.android.tools.metalava.model.visitors.TypeVisitor
+
+class PackageList(val packages: List<PackageItem>) {
+    fun accept(visitor: ItemVisitor) {
+        packages.forEach {
+            it.accept(visitor)
+        }
+    }
+
+    fun acceptTypes(visitor: TypeVisitor) {
+        packages.forEach {
+            it.acceptTypes(visitor)
+        }
+    }
+
+    /** All top level classes in all packages */
+    fun allTopLevelClasses(): Sequence<ClassItem> {
+        return packages.asSequence().flatMap { it.topLevelClasses() }
+    }
+
+    /** All top level classes **and inner classes** in all packages */
+    fun allClasses(): Sequence<ClassItem> {
+        return packages.asSequence().flatMap { it.allClasses() }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/model/ParameterItem.kt b/src/main/java/com/android/tools/metalava/model/ParameterItem.kt
new file mode 100644
index 0000000..6aa6e22
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/model/ParameterItem.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava.model
+
+import com.android.tools.metalava.model.visitors.ItemVisitor
+import com.android.tools.metalava.model.visitors.TypeVisitor
+
+interface ParameterItem : Item {
+    /** The name of this field */
+    fun name(): String
+
+    /** The type of this field */
+    fun type(): TypeItem
+
+    /** The containing method */
+    fun containingMethod(): MethodItem
+
+    /** Index of this parameter in the parameter list (0-based) */
+    val parameterIndex: Int
+
+    /**
+     * The public name of this parameter. In Kotlin, names are part of the
+     * public API; in Java they are not. In Java, you can annotate a
+     * parameter with {@literal @ParameterName("foo")} to name the parameter
+     * something (potentially different from the actual code parameter name).
+     */
+    fun publicName(): String?
+
+    override fun parent(): MethodItem? = containingMethod()
+
+    override fun accept(visitor: ItemVisitor) {
+        if (visitor.skip(this)) {
+            return
+        }
+
+        visitor.visitItem(this)
+        visitor.visitParameter(this)
+
+        visitor.afterVisitParameter(this)
+        visitor.afterVisitItem(this)
+    }
+
+    override fun acceptTypes(visitor: TypeVisitor) {
+        if (visitor.skip(this)) {
+            return
+        }
+
+        val type = type()
+        visitor.visitType(type, this)
+        visitor.afterVisitType(type, this)
+    }
+
+    override fun requiresNullnessInfo(): Boolean {
+        return !type().primitive
+    }
+
+    override fun hasNullnessInfo(): Boolean {
+        if (!requiresNullnessInfo()) {
+            return true
+        }
+
+        return modifiers.hasNullnessInfo()
+    }
+
+    // TODO: modifier list
+}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/model/TypeItem.kt b/src/main/java/com/android/tools/metalava/model/TypeItem.kt
new file mode 100644
index 0000000..937b5ec
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/model/TypeItem.kt
@@ -0,0 +1,266 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava.model
+
+import com.android.tools.lint.detector.api.ClassContext
+import com.android.tools.metalava.compatibility
+import com.android.tools.metalava.options
+import java.util.function.Predicate
+
+/** Represents a type */
+interface TypeItem {
+    /**
+     * Generates a string for this type.
+     *
+     * For a type like this: @Nullable java.util.List<@NonNull java.lang.String>,
+     * [outerAnnotations] controls whether the top level annotation like @Nullable
+     * is included, [innerAnnotations] controls whether annotations like @NonNull
+     * are included, and [erased] controls whether we return the string for
+     * the raw type, e.g. just "java.util.List"
+     *
+     * (The combination [outerAnnotations] = true and [innerAnnotations] = false
+     * is not allowed.)
+     */
+    fun toTypeString(
+        outerAnnotations: Boolean = false,
+        innerAnnotations: Boolean = outerAnnotations,
+        erased: Boolean = false
+    ): String
+
+    /** Alias for [toTypeString] with erased=true */
+    fun toErasedTypeString(): String
+
+    /** Returns the internal name of the type, as seen in bytecode */
+    fun internalName(): String {
+        // Default implementation; PSI subclass is more accurate
+        return toSlashFormat(toErasedTypeString())
+    }
+
+    fun asClass(): ClassItem?
+
+    fun toSimpleType(): String {
+        return toTypeString().replace("java.lang.", "")
+    }
+
+    val primitive: Boolean
+
+    fun typeArgumentClasses(): List<ClassItem>
+
+    fun convertType(from: ClassItem, to: ClassItem): TypeItem {
+        val map = from.mapTypeVariables(to)
+        if (!map.isEmpty()) {
+            return convertType(map)
+        }
+
+        return this
+    }
+
+    fun convertType(replacementMap: Map<String, String>?, owner: Item? = null): TypeItem
+
+    fun convertTypeString(replacementMap: Map<String, String>?): String {
+        return convertTypeString(toTypeString(outerAnnotations = true, innerAnnotations = true), replacementMap)
+    }
+
+    fun isJavaLangObject(): Boolean {
+        return toTypeString() == "java.lang.Object"
+    }
+
+    fun defaultValue(): Any? {
+        return when (toTypeString()) {
+            "boolean" -> false
+            "byte" -> 0.toByte()
+            "char" -> '\u0000'
+            "short" -> 0.toShort()
+            "int" -> 0
+            "long" -> 0L
+            "float" -> 0f
+            "double" -> 0.0
+            else -> null
+        }
+    }
+
+    /** Returns true if this type references a type not matched by the given predicate */
+    fun referencesExcludedType(filter: Predicate<Item>): Boolean {
+        if (primitive) {
+            return false
+        }
+
+        for (item in typeArgumentClasses()) {
+            if (!filter.test(item)) {
+                return true
+            }
+        }
+
+        return false
+    }
+
+    fun defaultValueString(): String = defaultValue()?.toString() ?: "null"
+
+    fun hasTypeArguments(): Boolean = toTypeString().contains("<")
+
+    fun isTypeParameter(): Boolean = toTypeString().length == 1 // heuristic; accurate implementation in PSI subclass
+
+    companion object {
+        private const val JAVA_LANG_PREFIX = "java.lang."
+        private const val ANDROID_SUPPORT_ANNOTATION_PREFIX = "@android.support.annotation."
+
+        fun shortenTypes(type: String): String {
+            if (options.omitCommonPackages &&
+                (type.contains("java.lang.") ||
+                        type.contains("@android.support.annotation."))
+            ) {
+                var cleaned = type
+                if (options.omitCommonPackages) {
+                    if (cleaned.contains(ANDROID_SUPPORT_ANNOTATION_PREFIX)) {
+                        cleaned = cleaned.replace(ANDROID_SUPPORT_ANNOTATION_PREFIX, "@")
+                    }
+                }
+
+                // Replacing java.lang is harder, since we don't want to operate in sub packages,
+                // e.g. java.lang.String -> String, but java.lang.reflect.Method -> unchanged
+                var index = cleaned.indexOf(JAVA_LANG_PREFIX)
+                while (index != -1) {
+                    val start = index + JAVA_LANG_PREFIX.length
+                    val end = cleaned.length
+                    for (index2 in start..end) {
+                        if (index2 == end) {
+                            val suffix = cleaned.substring(start)
+                            cleaned = if (index == 0) {
+                                suffix
+                            } else {
+                                cleaned.substring(0, index) + suffix
+                            }
+                            break
+                        }
+                        val c = cleaned[index2]
+                        if (c == '.') {
+                            break
+                        } else if (!Character.isJavaIdentifierPart(c)) {
+                            val suffix = cleaned.substring(start)
+                            cleaned = if (index == 0) {
+                                suffix
+                            } else {
+                                cleaned.substring(0, index) + suffix
+                            }
+                            break
+                        }
+                    }
+
+                    index = cleaned.indexOf(JAVA_LANG_PREFIX, start)
+                }
+
+                return cleaned
+            }
+
+            return type
+        }
+
+        fun formatType(type: String?): String {
+            if (type == null) {
+                return ""
+            }
+
+            var cleaned = type
+
+            if (compatibility.spacesAfterCommas && cleaned.indexOf(',') != -1) {
+                // The compat files have spaces after commas where we normally don't
+                cleaned = cleaned.replace(",", ", ").replace(",  ", ", ")
+            }
+
+            cleaned = cleanupGenerics(cleaned)
+            return cleaned
+        }
+
+        fun cleanupGenerics(signature: String): String {
+            // <T extends java.lang.Object> is the same as <T>
+            //  but NOT for <T extends Object & java.lang.Comparable> -- you can't
+            //  shorten this to <T & java.lang.Comparable
+            //return type.replace(" extends java.lang.Object", "")
+            return signature.replace(" extends java.lang.Object>", ">")
+
+        }
+
+        val comparator: Comparator<TypeItem> = Comparator { type1, type2 ->
+            val cls1 = type1.asClass()
+            val cls2 = type2.asClass()
+            if (cls1 != null && cls2 != null) {
+                ClassItem.fullNameComparator.compare(cls1, cls2)
+            } else {
+                type1.toTypeString().compareTo(type2.toTypeString())
+            }
+        }
+
+        fun convertTypeString(typeString: String, replacementMap: Map<String, String>?): String {
+            var string = typeString
+            if (replacementMap != null && replacementMap.isNotEmpty()) {
+                // This is a moved method (typically an implementation of an interface
+                // method provided in a hidden superclass), with generics signatures.
+                // We need to rewrite the generics variables in case they differ
+                // between the classes.
+                if (!replacementMap.isEmpty()) {
+                    replacementMap.forEach { from, to ->
+                        // We can't just replace one string at a time:
+                        // what if I have a map of {"A"->"B", "B"->"C"} and I tried to convert A,B,C?
+                        // If I do the replacements one letter at a time I end up with C,C,C; if I do the substitutions
+                        // simultaneously I get B,C,C. Therefore, we insert "___" as a magical prefix to prevent
+                        // scenarios like this, and then we'll drop them afterwards.
+                        string = string.replace(Regex(pattern = """\b$from\b"""), replacement = "___$to")
+                    }
+                }
+                string = string.replace("___", "")
+                return string
+            } else {
+                return string
+            }
+        }
+
+        // Copied from doclava1
+        fun toSlashFormat(typeName: String): String {
+            var name = typeName
+            var dimension = ""
+            while (name.endsWith("[]")) {
+                dimension += "["
+                name = name.substring(0, name.length - 2)
+            }
+
+            val base: String
+            if (name == "void") {
+                base = "V"
+            } else if (name == "byte") {
+                base = "B"
+            } else if (name == "boolean") {
+                base = "Z"
+            } else if (name == "char") {
+                base = "C"
+            } else if (name == "short") {
+                base = "S"
+            } else if (name == "int") {
+                base = "I"
+            } else if (name == "long") {
+                base = "L"
+            } else if (name == "float") {
+                base = "F"
+            } else if (name == "double") {
+                base = "D"
+            } else {
+                base = "L" + ClassContext.getInternalName(name) + ";"
+            }
+
+            return dimension + base
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/model/psi/ClassType.kt b/src/main/java/com/android/tools/metalava/model/psi/ClassType.kt
new file mode 100644
index 0000000..76710cd
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/model/psi/ClassType.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava.model.psi
+
+import com.intellij.psi.PsiClass
+
+enum class ClassType {
+    INTERFACE,
+    ENUM,
+    ANNOTATION_TYPE,
+    CLASS;
+
+    companion object {
+        fun getClassType(psiClass: PsiClass): ClassType {
+            return when {
+                psiClass.isAnnotationType -> ANNOTATION_TYPE
+                psiClass.isInterface -> INTERFACE
+                psiClass.isEnum -> ENUM
+                else -> CLASS
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/model/psi/Javadoc.kt b/src/main/java/com/android/tools/metalava/model/psi/Javadoc.kt
new file mode 100644
index 0000000..31620b5
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/model/psi/Javadoc.kt
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava.model.psi
+
+import com.intellij.psi.JavaDocTokenType
+import com.intellij.psi.JavaPsiFacade
+import com.intellij.psi.PsiElement
+import com.intellij.psi.javadoc.PsiDocComment
+import com.intellij.psi.javadoc.PsiDocTag
+import com.intellij.psi.javadoc.PsiDocToken
+
+/*
+ * Various utilities for merging comments into existing javadoc sections.
+ *
+ * TODO: Handle KDoc
+ */
+
+/**
+ * Merges the given [newText] into the existing documentation block [existingDoc]
+ * (which should be a full documentation node, including the surrounding comment
+ * start and end tokens.)
+ *
+ * If the [tagSection] is null, add the comment to the initial text block
+ * of the description. Otherwise if it is "@return", add the comment
+ * to the return value. Otherwise the [tagSection] is taken to be the
+ * parameter name, and the comment added as parameter documentation
+ * for the given parameter.
+ */
+fun mergeDocumentation(
+    existingDoc: String,
+    psiElement: PsiElement,
+    newText: String,
+    tagSection: String?,
+    append: Boolean
+): String {
+
+    if (existingDoc.isBlank()) {
+        // There's no existing comment: Create a new one. This is easy.
+        val content = when {
+            tagSection == "@return" -> "@return $newText"
+            tagSection?.startsWith("@") ?: false -> "$tagSection $newText"
+            tagSection != null -> "@param $tagSection $newText"
+            else -> newText
+        }
+
+        // TODO: Handle prefixing "*" on lines, if already done in the document?
+        return if (newText.contains('\n')) {
+            "/** $content */"
+        } else {
+            return insertInto("/**\n */", content, 3)
+        }
+    }
+
+    val doc = trimDocIndent(existingDoc)
+
+    // We'll use the PSI Javadoc support to parse the documentation
+    // to help us scan the tokens in the documentation, such that
+    // we don't have to search for raw substrings like "@return" which
+    // can incorrectly find matches in escaped code snippets etc.
+    val factory = JavaPsiFacade.getElementFactory(psiElement.project)
+            ?: error("Invalid tool configuration; did not find JavaPsiFacade factory")
+    val docComment = factory.createDocCommentFromText(doc)
+
+    if (tagSection == "@return") {
+        // Add in return value
+        val returnTag = docComment.findTagByName("return")
+        if (returnTag == null) {
+            // Find last tag
+            val lastTag = findLastTag(docComment)
+            val offset = if (lastTag != null) {
+                findTagEnd(lastTag)
+            } else {
+                doc.length - 2
+            }
+            return insertInto(doc, "@return $newText", offset)
+        } else {
+            // Add text to the existing @return tag
+            val offset = if (append)
+                findTagEnd(returnTag)
+            else
+                returnTag.textRange.startOffset + returnTag.name.length + 1
+            return insertInto(doc, newText, offset)
+        }
+    } else if (tagSection != null) {
+        val parameter = if (tagSection.startsWith("@"))
+            docComment.findTagByName(tagSection.substring(1))
+        else
+            findParamTag(docComment, tagSection)
+        if (parameter == null) {
+            // Add new parameter or tag
+            // TODO: Decide whether to place it alphabetically or place it by parameter order
+            // in the signature. Arguably I should follow the convention already present in the
+            // doc, if any
+            // For now just appending to the last tag before the return tag (if any).
+            // This actually works out well in practice where arguments are generally all documented
+            // or all not documented; when none of the arguments are documented these end up appending
+            // exactly in the right parameter order!
+            val returnTag = docComment.findTagByName("return")
+            val anchor = returnTag ?: findLastTag(docComment)
+            val offset = when {
+                returnTag != null -> returnTag.textRange.startOffset
+                anchor != null -> findTagEnd(anchor)
+                else -> doc.length - 2  // "*/
+            }
+            val tagName = if (tagSection.startsWith("@")) tagSection else "@param $tagSection"
+            return insertInto(doc, "$tagName $newText", offset)
+        } else {
+            // Add to existing tag/parameter
+            val offset = if (append)
+                findTagEnd(parameter)
+            else
+                parameter.textRange.startOffset + parameter.name.length + 1
+            return insertInto(doc, newText, offset)
+        }
+    } else {
+        // Add to the main text section of the comment.
+        val firstTag = findFirstTag(docComment)
+        val startOffset =
+            if (!append) {
+                4 // "/** ".length
+            } else if (firstTag != null) {
+                firstTag.textRange.startOffset
+            } else {
+                doc.length - 2 // -2: end marker */
+            }
+        return insertInto(doc, newText, startOffset)
+    }
+}
+
+fun findParamTag(docComment: PsiDocComment, paramName: String): PsiDocTag? {
+    return docComment.findTagsByName("param").firstOrNull { it.valueElement?.text == paramName }
+}
+
+fun findFirstTag(docComment: PsiDocComment): PsiDocTag? {
+    return docComment.tags.asSequence().minBy { it.textRange.startOffset }
+}
+
+fun findLastTag(docComment: PsiDocComment): PsiDocTag? {
+    return docComment.tags.asSequence().maxBy { it.textRange.startOffset }
+}
+
+fun findTagEnd(tag: PsiDocTag): Int {
+    var curr: PsiElement? = tag.nextSibling
+    while (curr != null) {
+        if (curr is PsiDocToken && curr.tokenType == JavaDocTokenType.DOC_COMMENT_END) {
+            return curr.textRange.startOffset
+        } else if (curr is PsiDocTag) {
+            return curr.textRange.startOffset
+        }
+
+        curr = curr.nextSibling
+    }
+
+    return tag.textRange.endOffset
+}
+
+fun trimDocIndent(existingDoc: String): String {
+    val index = existingDoc.indexOf('\n')
+    if (index == -1) {
+        return existingDoc
+    }
+
+    return existingDoc.substring(0, index + 1) +
+            existingDoc.substring(index + 1).trimIndent().split('\n').joinToString(separator = "\n") {
+                if (!it.startsWith(" ")) {
+                    " ${it.trimEnd()}"
+                } else {
+                    it.trimEnd()
+                }
+            }
+}
+
+fun insertInto(existingDoc: String, newText: String, initialOffset: Int): String {
+    // TODO: Insert "." between existing documentation and new documentation, if necessary.
+
+    val offset = if (initialOffset > 4 && existingDoc.regionMatches(initialOffset - 4, "\n * ", 0, 4, false)) {
+        initialOffset - 4
+    } else {
+        initialOffset
+    }
+    val index = existingDoc.indexOf('\n')
+    val prefixWithStar = index == -1 || existingDoc[index + 1] == '*' ||
+            existingDoc[index + 1] == ' ' && existingDoc[index + 2] == '*'
+
+    val prefix = existingDoc.substring(0, offset)
+    val suffix = existingDoc.substring(offset)
+    val startSeparator = "\n"
+    val endSeparator =
+        if (suffix.startsWith("\n") || suffix.startsWith(" \n")) "" else if (suffix == "*/") "\n" else if (prefixWithStar) "\n * " else "\n"
+
+    val middle = if (prefixWithStar) {
+        startSeparator + newText.split('\n').joinToString(separator = "\n") { " * $it" } +
+                endSeparator
+    } else {
+        "$startSeparator$newText$endSeparator"
+    }
+
+    // Going from single-line to multi-line?
+    return if (existingDoc.indexOf('\n') == -1 && existingDoc.startsWith("/** ")) {
+        prefix.substring(0, 3) + "\n *" + prefix.substring(3) + middle +
+                if (suffix == "*/") " */" else suffix
+    } else {
+        prefix + middle + suffix
+    }
+}
diff --git a/src/main/java/com/android/tools/metalava/model/psi/PsiAnnotationItem.kt b/src/main/java/com/android/tools/metalava/model/psi/PsiAnnotationItem.kt
new file mode 100644
index 0000000..b8ebb45
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/model/psi/PsiAnnotationItem.kt
@@ -0,0 +1,381 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava.model.psi
+
+import com.android.SdkConstants.ATTR_VALUE
+import com.android.tools.lint.detector.api.ConstantEvaluator
+import com.android.tools.metalava.XmlBackedAnnotationItem
+import com.android.tools.metalava.model.AnnotationArrayAttributeValue
+import com.android.tools.metalava.model.AnnotationAttribute
+import com.android.tools.metalava.model.AnnotationAttributeValue
+import com.android.tools.metalava.model.AnnotationItem
+import com.android.tools.metalava.model.AnnotationSingleAttributeValue
+import com.android.tools.metalava.model.ClassItem
+import com.android.tools.metalava.model.Codebase
+import com.android.tools.metalava.model.Item
+import com.android.tools.metalava.model.canonicalizeFloatingPointString
+import com.android.tools.metalava.model.javaEscapeString
+import com.intellij.psi.PsiAnnotation
+import com.intellij.psi.PsiAnnotationMemberValue
+import com.intellij.psi.PsiArrayInitializerMemberValue
+import com.intellij.psi.PsiBinaryExpression
+import com.intellij.psi.PsiClass
+import com.intellij.psi.PsiExpression
+import com.intellij.psi.PsiField
+import com.intellij.psi.PsiLiteral
+import com.intellij.psi.PsiMethod
+import com.intellij.psi.PsiReference
+import com.intellij.psi.impl.JavaConstantExpressionEvaluator
+import org.jetbrains.kotlin.asJava.elements.KtLightNullabilityAnnotation
+
+class PsiAnnotationItem private constructor(
+    override val codebase: PsiBasedCodebase,
+    val psiAnnotation: PsiAnnotation
+) : AnnotationItem {
+    private var attributes: List<AnnotationAttribute>? = null
+
+    override fun toString(): String = toSource()
+
+    override fun toSource(): String {
+        val qualifiedName = qualifiedName() ?: return ""
+
+        val attributes = psiAnnotation.parameterList.attributes
+        if (attributes.isEmpty()) {
+            return "@" + qualifiedName
+        }
+
+        val sb = StringBuilder(30)
+        sb.append("@")
+        sb.append(qualifiedName)
+        sb.append("(")
+        if (attributes.size == 1 && (attributes[0].name == null || attributes[0].name == ATTR_VALUE)) {
+            // Special case: omit "value" if it's the only attribute
+            appendValue(sb, attributes[0].value)
+        } else {
+            var first = true
+            for (attribute in attributes) {
+                if (first) {
+                    first = false
+                } else {
+                    sb.append(", ")
+                }
+                sb.append(attribute.name ?: ATTR_VALUE)
+                sb.append('=')
+                appendValue(sb, attribute.value)
+            }
+        }
+        sb.append(")")
+
+        return sb.toString()
+    }
+
+    override fun resolve(): ClassItem? {
+        return codebase.findClass(psiAnnotation.qualifiedName ?: return null)
+    }
+
+    private fun appendValue(sb: StringBuilder, value: PsiAnnotationMemberValue?) {
+        // Compute annotation string -- we don't just use value.text here
+        // because that may not use fully qualified names, e.g. the source may say
+        //  @RequiresPermission(Manifest.permission.ACCESS_COARSE_LOCATION)
+        // and we want to compute
+        //  @android.support.annotation.RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION)
+        when (value) {
+            null -> sb.append("null")
+            is PsiLiteral -> sb.append(literalToString(value.value))
+            is PsiReference -> {
+                val resolved = value.resolve()
+                when (resolved) {
+                    is PsiField -> {
+                        val containing = resolved.containingClass
+                        if (containing != null) {
+                            // If it's a field reference, see if it looks like the field is hidden; if
+                            // so, inline the value
+                            val cls = codebase.findOrCreateClass(containing)
+                            val initializer = resolved.initializer
+                            if (initializer != null) {
+                                val fieldItem = cls.findField(resolved.name)
+                                if (fieldItem == null || fieldItem.isHiddenOrRemoved()) {
+                                    // Use the literal value instead
+                                    val source = getConstantSource(initializer)
+                                    if (source != null) {
+                                        sb.append(source)
+                                        return
+                                    }
+                                }
+                            }
+                            containing.qualifiedName?.let {
+                                sb.append(it).append('.')
+                            }
+                        }
+
+                        sb.append(resolved.name)
+                    }
+                    is PsiClass -> resolved.qualifiedName?.let { sb.append(it) }
+                    else -> {
+                        sb.append(value.text)
+                    }
+                }
+            }
+            is PsiBinaryExpression -> {
+                appendValue(sb, value.lOperand)
+                sb.append(' ')
+                sb.append(value.operationSign.text)
+                sb.append(' ')
+                appendValue(sb, value.rOperand)
+            }
+            is PsiArrayInitializerMemberValue -> {
+                sb.append('{')
+                var first = true
+                for (initializer in value.initializers) {
+                    if (first) {
+                        first = false
+                    } else {
+                        sb.append(", ")
+                    }
+                    appendValue(sb, initializer)
+                }
+                sb.append('}')
+            }
+            else -> {
+                if (value is PsiExpression) {
+                    val source = getConstantSource(value)
+                    if (source != null) {
+                        sb.append(source)
+                        return
+                    }
+                }
+                sb.append(value.text)
+            }
+        }
+    }
+
+    override fun isNonNull(): Boolean {
+        if (psiAnnotation is KtLightNullabilityAnnotation &&
+            psiAnnotation.qualifiedName == ""
+        ) {
+            // Hack/workaround: some UAST annotation nodes do not provide qualified name :=(
+            return true
+        }
+        return super.isNonNull()
+    }
+
+    private fun getConstantSource(value: PsiExpression): String? {
+        val constant = JavaConstantExpressionEvaluator.computeConstantExpression(value, false)
+        return when (constant) {
+            is Int -> "0x${Integer.toHexString(constant)}"
+            is String -> "\"${javaEscapeString(constant)}\""
+            is Long -> "${constant}L"
+            is Boolean -> constant.toString()
+            is Byte -> Integer.toHexString(constant.toInt())
+            is Short -> Integer.toHexString(constant.toInt())
+            is Float -> {
+                when (constant) {
+                    Float.POSITIVE_INFINITY -> "Float.POSITIVE_INFINITY"
+                    Float.NEGATIVE_INFINITY -> "Float.NEGATIVE_INFINITY"
+                    Float.NaN -> "Float.NaN"
+                    else -> {
+                        "${canonicalizeFloatingPointString(constant.toString())}F"
+                    }
+                }
+            }
+            is Double -> {
+                when (constant) {
+                    Double.POSITIVE_INFINITY -> "Double.POSITIVE_INFINITY"
+                    Double.NEGATIVE_INFINITY -> "Double.NEGATIVE_INFINITY"
+                    Double.NaN -> "Double.NaN"
+                    else -> {
+                        canonicalizeFloatingPointString(constant.toString())
+                    }
+                }
+            }
+            is Char -> {
+                "'${javaEscapeString(constant.toString())}'"
+            }
+            else -> {
+                null
+            }
+        }
+    }
+
+    private fun literalToString(value: Any?): String {
+        if (value == null) {
+            return "null"
+        }
+
+        when (value) {
+            is Int -> {
+                return value.toString()
+            }
+            is String -> {
+                return "\"${javaEscapeString(value)}\""
+            }
+            is Long -> {
+                return value.toString() + "L"
+            }
+            is Boolean -> {
+                return value.toString()
+            }
+            is Byte -> {
+                return Integer.toHexString(value.toInt())
+            }
+            is Short -> {
+                return Integer.toHexString(value.toInt())
+            }
+            is Float -> {
+                return when (value) {
+                    Float.POSITIVE_INFINITY -> "(1.0f/0.0f)"
+                    Float.NEGATIVE_INFINITY -> "(-1.0f/0.0f)"
+                    Float.NaN -> "(0.0f/0.0f)"
+                    else -> {
+                        canonicalizeFloatingPointString(value.toString()) + "f"
+                    }
+                }
+            }
+            is Double -> {
+                return when (value) {
+                    Double.POSITIVE_INFINITY -> "(1.0/0.0)"
+                    Double.NEGATIVE_INFINITY -> "(-1.0/0.0)"
+                    Double.NaN -> "(0.0/0.0)"
+                    else -> {
+                        canonicalizeFloatingPointString(value.toString())
+                    }
+                }
+            }
+            is Char -> {
+                return String.format("'%s'", javaEscapeString(value.toString()))
+            }
+        }
+
+        return value.toString()
+    }
+
+    override fun qualifiedName() = AnnotationItem.mapName(codebase, psiAnnotation.qualifiedName)
+
+    override fun attributes(): List<AnnotationAttribute> {
+        if (attributes == null) {
+            val psiAttributes = psiAnnotation.parameterList.attributes
+            attributes = if (psiAttributes.isEmpty()) {
+                emptyList()
+            } else {
+                val list = mutableListOf<AnnotationAttribute>()
+                for (parameter in psiAttributes) {
+                    list.add(
+                        PsiAnnotationAttribute(
+                            codebase,
+                            parameter.name ?: ATTR_VALUE, parameter.value ?: continue
+                        )
+                    )
+                }
+                list
+            }
+        }
+
+        return attributes!!
+    }
+
+    companion object {
+        fun create(codebase: PsiBasedCodebase, psiAnnotation: PsiAnnotation): PsiAnnotationItem {
+            return PsiAnnotationItem(codebase, psiAnnotation)
+        }
+
+        fun create(codebase: PsiBasedCodebase, original: PsiAnnotationItem): PsiAnnotationItem {
+            return PsiAnnotationItem(codebase, original.psiAnnotation)
+        }
+
+        // TODO: Inline this such that instead of constructing XmlBackedAnnotationItem
+        // and then producing source and parsing it, produce source directly
+        fun create(
+            codebase: Codebase, xmlAnnotation: XmlBackedAnnotationItem,
+            context: Item? = null
+        ): PsiAnnotationItem {
+            if (codebase is PsiBasedCodebase) {
+                return codebase.createAnnotation(xmlAnnotation.toSource(), context)
+            } else {
+                codebase.unsupported("Converting to PSI annotation requires PSI codebase")
+            }
+        }
+    }
+}
+
+class PsiAnnotationAttribute(
+    codebase: PsiBasedCodebase,
+    override val name: String,
+    psiValue: PsiAnnotationMemberValue
+) : AnnotationAttribute {
+    override val value: AnnotationAttributeValue = PsiAnnotationValue.create(
+        codebase, psiValue
+    )
+}
+
+abstract class PsiAnnotationValue : AnnotationAttributeValue {
+    companion object {
+        fun create(codebase: PsiBasedCodebase, value: PsiAnnotationMemberValue): PsiAnnotationValue {
+            return if (value is PsiArrayInitializerMemberValue) {
+                PsiAnnotationArrayAttributeValue(codebase, value)
+            } else {
+                PsiAnnotationSingleAttributeValue(codebase, value)
+            }
+        }
+    }
+
+    override fun toString(): String = toSource()
+}
+
+class PsiAnnotationSingleAttributeValue(
+    private val codebase: PsiBasedCodebase,
+    private val psiValue: PsiAnnotationMemberValue
+) : PsiAnnotationValue(), AnnotationSingleAttributeValue {
+    override val valueSource: String = psiValue.text
+    override val value: Any?
+        get() {
+            if (psiValue is PsiLiteral) {
+                return psiValue.value
+            }
+
+            val value = ConstantEvaluator.evaluate(null, psiValue)
+            if (value != null) {
+                return value
+            }
+
+            return psiValue.text
+        }
+
+    override fun value(): Any? = value
+
+    override fun toSource(): String = psiValue.text
+
+    override fun resolve(): Item? {
+        if (psiValue is PsiReference) {
+            val resolved = psiValue.resolve()
+            when (resolved) {
+                is PsiField -> return codebase.findField(resolved)
+                is PsiClass -> return codebase.findOrCreateClass(resolved)
+                is PsiMethod -> return codebase.findMethod(resolved)
+            }
+        }
+        return null
+    }
+}
+
+class PsiAnnotationArrayAttributeValue(codebase: PsiBasedCodebase, private val value: PsiArrayInitializerMemberValue) :
+    PsiAnnotationValue(), AnnotationArrayAttributeValue {
+    override val values = value.initializers.map {
+        create(codebase, it)
+    }.toList()
+
+    override fun toSource(): String = value.text
+}
diff --git a/src/main/java/com/android/tools/metalava/model/psi/PsiBasedCodebase.kt b/src/main/java/com/android/tools/metalava/model/psi/PsiBasedCodebase.kt
new file mode 100644
index 0000000..de788ff
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/model/psi/PsiBasedCodebase.kt
@@ -0,0 +1,974 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava.model.psi
+
+import com.android.SdkConstants
+import com.android.tools.metalava.PackageDocs
+import com.android.tools.metalava.compatibility
+import com.android.tools.metalava.doclava1.Errors
+import com.android.tools.metalava.model.ClassItem
+import com.android.tools.metalava.model.Codebase
+import com.android.tools.metalava.model.DefaultCodebase
+import com.android.tools.metalava.model.FieldItem
+import com.android.tools.metalava.model.Item
+import com.android.tools.metalava.model.MethodItem
+import com.android.tools.metalava.model.PackageItem
+import com.android.tools.metalava.model.PackageList
+import com.android.tools.metalava.model.TypeItem
+import com.android.tools.metalava.options
+import com.android.tools.metalava.reporter
+import com.android.tools.metalava.tick
+import com.google.common.collect.BiMap
+import com.google.common.collect.HashBiMap
+import com.intellij.openapi.project.Project
+import com.intellij.psi.JavaPsiFacade
+import com.intellij.psi.PsiAnnotation
+import com.intellij.psi.PsiClass
+import com.intellij.psi.PsiClassOwner
+import com.intellij.psi.PsiClassType
+import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiField
+import com.intellij.psi.PsiFile
+import com.intellij.psi.PsiJavaCodeReferenceElement
+import com.intellij.psi.PsiJavaFile
+import com.intellij.psi.PsiMethod
+import com.intellij.psi.PsiPackage
+import com.intellij.psi.PsiType
+import com.intellij.psi.PsiTypeParameter
+import com.intellij.psi.javadoc.PsiDocComment
+import com.intellij.psi.javadoc.PsiDocTag
+import com.intellij.psi.search.GlobalSearchScope
+import com.intellij.psi.util.PsiTreeUtil
+import org.intellij.lang.annotations.Language
+import org.jetbrains.uast.UFile
+import org.jetbrains.uast.UastContext
+import java.io.File
+import java.io.IOException
+import java.util.ArrayDeque
+import java.util.ArrayList
+import java.util.HashMap
+import java.util.function.Predicate
+import java.util.zip.ZipFile
+
+const val PACKAGE_ESTIMATE = 400
+const val CLASS_ESTIMATE = 12000
+const val METHOD_ESTIMATE = 1000
+
+open class PsiBasedCodebase(override var description: String = "Unknown") : DefaultCodebase() {
+    lateinit var project: Project
+
+    /** Map from class name to class item */
+    protected val classMap: MutableMap<String, PsiClassItem> = HashMap(CLASS_ESTIMATE)
+
+    /** Map from psi type to type item */
+    private val typeMap: MutableMap<PsiType, TypeItem> = HashMap(400)
+
+    /**
+     * Map from classes to the set of methods for each (but only for classes where we've
+     * called [findMethod]
+     */
+    private lateinit var methodMap: MutableMap<PsiClassItem, MutableMap<PsiMethod, PsiMethodItem>>
+
+    /** Map from package name to the corresponding package item */
+    private lateinit var packageMap: MutableMap<String, PsiPackageItem>
+
+    /** Map from package name to list of classes in that package */
+    private lateinit var packageClasses: MutableMap<String, MutableList<PsiClassItem>>
+
+    /** A set of packages to hide */
+    private lateinit var hiddenPackages: MutableMap<String, Boolean?>
+
+    private var initializing = false
+
+    override fun trustedApi(): Boolean = false
+
+    private var packageDocs: PackageDocs? = null
+
+    private var hideClassesFromJars = true
+
+    private lateinit var emptyPackage: PsiPackageItem
+
+    fun initialize(project: Project, units: List<PsiFile>, packages: PackageDocs) {
+        initializing = true
+        this.units = units
+        packageDocs = packages
+
+        this.project = project
+        // there are currently ~230 packages in the public SDK, but here we need to account for internal ones too
+        val hiddenPackages: MutableSet<String> = packages.hiddenPackages
+        val packageDocs: MutableMap<String, String> = packages.packageDocs
+        this.hiddenPackages = HashMap(100)
+        for (pkgName in hiddenPackages) {
+            this.hiddenPackages[pkgName] = true
+        }
+
+        packageMap = HashMap(PACKAGE_ESTIMATE)
+        packageClasses = HashMap(PACKAGE_ESTIMATE)
+        packageClasses[""] = ArrayList()
+        this.methodMap = HashMap(METHOD_ESTIMATE)
+
+        for (unit in units) {
+            tick() // show progress
+
+            val topLevelClasses = mutableListOf<ClassItem>()
+            var classes = (unit as? PsiClassOwner)?.classes?.toList() ?: emptyList()
+            if (classes.isEmpty()) {
+                val uastContext = project.getComponent(UastContext::class.java)
+                val uFile = uastContext.convertElementWithParent(unit, UFile::class.java) as? UFile?
+                classes = uFile?.classes?.map { it }?.toList() ?: emptyList()
+            }
+            var packageName: String? = null
+            if (classes.isEmpty() && unit is PsiJavaFile) {
+                val packageStatement = unit.packageStatement
+                // Look for javadoc on the package statement; this is NOT handed to us on
+                // the PsiPackage!
+                if (packageStatement != null) {
+                    packageName = packageStatement.packageName
+                    val comment = PsiTreeUtil.getPrevSiblingOfType(packageStatement, PsiDocComment::class.java)
+                    if (comment != null) {
+                        val text = comment.text
+                        if (text.contains("@hide")) {
+                            hiddenPackages.add(packageName)
+                        }
+                        packageDocs[packageName] = text + (packageDocs[packageName] ?: "")
+                    }
+                }
+            } else {
+                for (psiClass in classes) {
+                    val classItem = createClass(psiClass)
+                    topLevelClasses.add(classItem)
+
+                    if (packageName == null) {
+                        packageName = getPackageName(psiClass)
+                    }
+                }
+            }
+        }
+
+        // Next construct packages
+        for ((pkgName, classes) in packageClasses) {
+            tick() // show progress
+            val psiPackage = JavaPsiFacade.getInstance(project).findPackage(pkgName)
+            if (psiPackage == null) {
+                println("Could not find package $pkgName")
+                continue
+            }
+
+            val sortedClasses = classes.toMutableList().sortedWith(ClassItem.fullNameComparator)
+            val packageHtml = packageDocs[pkgName]
+            registerPackage(psiPackage, sortedClasses, packageHtml, pkgName)
+        }
+
+        initializing = false
+
+        emptyPackage = findPackage("")!!
+
+        // Finish initialization
+        val initialPackages = ArrayList(packageMap.values)
+        var registeredCount = packageMap.size // classes added after this point will have indices >= original
+        for (cls in initialPackages) {
+            cls.finishInitialization()
+        }
+
+        // Finish initialization of any additional classes that were registered during
+        // the above initialization (recursively)
+        while (registeredCount < packageMap.size) {
+            val added = packageMap.values.minus(initialPackages)
+            registeredCount = packageMap.size
+            for (pkg in added) {
+                pkg.finishInitialization()
+            }
+        }
+
+        // Point to "parent" packages, since doclava treats packages as nested (e.g. an @hide on
+        // android.foo will also apply to android.foo.bar)
+        addParentPackages(packageMap.values)
+    }
+
+    private fun addParentPackages(packages: Collection<PsiPackageItem>) {
+        val missingPackages = packages.mapNotNull {
+            val name = it.qualifiedName()
+            val index = name.lastIndexOf('.')
+            val parent = if (index != -1) {
+                name.substring(0, index)
+            } else {
+                ""
+            }
+            if (packageMap.containsKey(parent)) {
+                // Already registered
+                null
+            } else {
+                parent
+            }
+        }.toSet()
+
+        // Create PackageItems for any packages that weren't in the source
+        for (pkgName in missingPackages) {
+            val psiPackage = JavaPsiFacade.getInstance(project).findPackage(pkgName) ?: continue
+            val sortedClasses = emptyList<PsiClassItem>()
+            val packageHtml = null
+            val pkg = registerPackage(psiPackage, sortedClasses, packageHtml, pkgName)
+            pkg.emit = false // don't expose these packages in the API signature files, stubs, etc
+        }
+
+        // Connect up all the package items
+        for (pkg in packageMap.values) {
+            var name = pkg.qualifiedName()
+            // Find parent package; we have to loop since we don't always find a PSI package
+            // for intermediate elements; e.g. we may jump from java.lang straing up to the default
+            // package
+            while (name.isNotEmpty()) {
+                val index = name.lastIndexOf('.')
+                if (index != -1) {
+                    name = name.substring(0, index)
+                } else {
+                    name = ""
+                }
+                val parent = findPackage(name) ?: continue
+                pkg.containingPackageField = parent
+                break
+            }
+        }
+    }
+
+    private fun registerPackage(
+        psiPackage: PsiPackage,
+        sortedClasses: List<PsiClassItem>?,
+        packageHtml: String?,
+        pkgName: String
+    ): PsiPackageItem {
+        val packageItem = PsiPackageItem.create(
+            this, psiPackage,
+            packageHtml
+        )
+        packageMap[pkgName] = packageItem
+        if (isPackageHidden(pkgName)) {
+            packageItem.hidden = true
+        }
+
+        sortedClasses?.let { packageItem.addClasses(it) }
+        return packageItem
+    }
+
+    fun initialize(project: Project, jarFile: File) {
+        initializing = true
+        hideClassesFromJars = false
+
+        this.project = project
+
+        // Find all classes referenced from the class
+        val facade = JavaPsiFacade.getInstance(project)
+        val scope = GlobalSearchScope.allScope(project)
+
+        hiddenPackages = HashMap(100)
+        packageMap = HashMap(PACKAGE_ESTIMATE)
+        packageClasses = HashMap(PACKAGE_ESTIMATE)
+        packageClasses[""] = ArrayList()
+        this.methodMap = HashMap(1000)
+        val packageToClasses: MutableMap<String, MutableList<PsiClassItem>> = HashMap(
+            PACKAGE_ESTIMATE
+        )
+        packageToClasses[""] = ArrayList() // ensure we construct one for the default package
+
+        val topLevelClasses = ArrayList<ClassItem>(CLASS_ESTIMATE)
+
+        try {
+            ZipFile(jarFile).use({ jar ->
+                val enumeration = jar.entries()
+                while (enumeration.hasMoreElements()) {
+                    val entry = enumeration.nextElement()
+                    val fileName = entry.name
+                    if (fileName.contains("$")) {
+                        // skip inner classes
+                        continue
+                    }
+                    if (fileName.endsWith(SdkConstants.DOT_CLASS)) {
+                        val qualifiedName = fileName.removeSuffix(SdkConstants.DOT_CLASS).replace('/', '.')
+                        if (qualifiedName.endsWith(".package-info")) {
+                            // Ensure we register a package for this, even if empty
+                            val packageName = qualifiedName.removeSuffix(".package-info")
+                            var list = packageToClasses[packageName]
+                            if (list == null) {
+                                list = mutableListOf()
+                                packageToClasses[packageName] = list
+                            }
+                            continue
+                        } else {
+                            val psiClass = facade.findClass(qualifiedName, scope) ?: continue
+
+                            val classItem = createClass(psiClass)
+                            topLevelClasses.add(classItem)
+
+                            val packageName = getPackageName(psiClass)
+                            var list = packageToClasses[packageName]
+                            if (list == null) {
+                                list = mutableListOf(classItem)
+                                packageToClasses[packageName] = list
+                            } else {
+                                list.add(classItem)
+                            }
+                        }
+                    }
+                }
+            })
+        } catch (e: IOException) {
+            reporter.report(Errors.IO_ERROR, jarFile, e.message ?: e.toString())
+        }
+
+        // Next construct packages
+        for ((pkgName, packageClasses) in packageToClasses) {
+            val psiPackage = JavaPsiFacade.getInstance(project).findPackage(pkgName)
+            if (psiPackage == null) {
+                println("Could not find package $pkgName")
+                continue
+            }
+
+            packageClasses.sortWith(ClassItem.fullNameComparator)
+            // TODO: How do we obtain the package docs? We generally don't have them, but it *would* be
+            // nice if we picked up "overview.html" bundled files and added them. But since the docs
+            // are generally missing for all elements *anyway*, let's not bother.
+            val packageHtml: String? = packageDocs?.packageDocs!![pkgName]
+            registerPackage(psiPackage, packageClasses, packageHtml, pkgName)
+        }
+
+        emptyPackage = findPackage("")!!
+
+        initializing = false
+        hideClassesFromJars = true
+
+        // Finish initialization
+        for (pkg in packageMap.values) {
+            pkg.finishInitialization()
+        }
+
+    }
+
+    fun dumpStats() {
+        options.stdout.println(
+            "INTERNAL STATS: Size of classMap=${classMap.size} and size of " +
+                    "methodMap=${methodMap.size} and size of packageMap=${packageMap.size}, and the " +
+                    "typemap size is ${typeMap.size}, and the packageClasses size is ${packageClasses.size} "
+        )
+    }
+
+    private fun registerPackageClass(packageName: String, cls: PsiClassItem) {
+        var list = packageClasses[packageName]
+        if (list == null) {
+            list = ArrayList()
+            packageClasses[packageName] = list
+        }
+
+        if (isPackageHidden(packageName)) {
+            cls.hidden = true
+        }
+
+        list.add(cls)
+    }
+
+    private fun isPackageHidden(packageName: String): Boolean {
+        val hidden = hiddenPackages[packageName]
+        if (hidden == true) {
+            return true
+        } else if (hidden == null) {
+            // Compute for all prefixes of this package
+            var pkg = packageName
+            while (true) {
+                if (hiddenPackages[pkg] != null) {
+                    hiddenPackages[packageName] = hiddenPackages[pkg]
+                    if (hiddenPackages[pkg] == true) {
+                        return true
+                    }
+                }
+                val last = pkg.lastIndexOf('.')
+                if (last == -1 || !compatibility.inheritPackageDocs) {
+                    hiddenPackages[packageName] = false
+                    break
+                } else {
+                    pkg = pkg.substring(0, last)
+                }
+            }
+        }
+
+        return false
+    }
+
+    private fun createClass(clz: PsiClass): PsiClassItem {
+        val classItem = PsiClassItem.create(this, clz)
+
+        if (!initializing && options.hideClasspathClasses) {
+            // This class is found while we're no longer initializing all the source units:
+            // that means it must be found on the classpath instead. These should be treated
+            // as hidden; we don't want to generate code for them.
+            classItem.emit = false
+
+            // Workaround: we're pulling in .aidl files from .jar files. These are
+            // marked @hide, but since we only see the .class files we don't know that.
+            if (classItem.simpleName().startsWith("I") &&
+                classItem.isFromClassPath() &&
+                clz.interfaces.any { it.qualifiedName == "android.os.IInterface" }
+            ) {
+                classItem.hidden = true
+            }
+        }
+
+        if (clz is PsiTypeParameter) {
+            // Don't put PsiTypeParameter classes into the registry; e.g. when we're visiting
+            //  java.util.stream.Stream<R>
+            // we come across "R" and would try to place it here.
+            classItem.containingPackage = emptyPackage
+            return classItem
+        }
+        val qualifiedName: String = clz.qualifiedName ?: clz.name!!
+        classMap[qualifiedName] = classItem
+
+        // TODO: Cache for adjacent files!
+        val packageName = getPackageName(clz)
+        registerPackageClass(packageName, classItem)
+
+        if (!initializing) {
+            classItem.emit = false
+            classItem.finishInitialization()
+            val pkgName = getPackageName(clz)
+            val pkg = findPackage(pkgName)
+            if (pkg == null) {
+                //val packageHtml: String? = packageDocs?.packageDocs!![pkgName]
+                // dynamically discovered packages should NOT be included
+                //val packageHtml = "/** @hide */"
+                val packageHtml = null
+                val psiPackage = JavaPsiFacade.getInstance(project).findPackage(pkgName)
+                if (psiPackage != null) {
+                    val packageItem = registerPackage(psiPackage, null, packageHtml, pkgName)
+                    // Don't include packages from API that isn't directly included in the API
+                    if (options.hideClasspathClasses) {
+                        packageItem.emit = false
+                    }
+                    packageItem.addClass(classItem)
+                }
+            } else {
+                pkg.addClass(classItem)
+            }
+        }
+
+        return classItem
+    }
+
+    override fun getPackages(): PackageList {
+        // TODO: Sorting is probably not necessary here!
+        return PackageList(packageMap.values.toMutableList().sortedWith(PackageItem.comparator))
+    }
+
+    override fun size(): Int {
+        return packageMap.size
+    }
+
+    override fun findPackage(pkgName: String): PsiPackageItem? {
+        return packageMap[pkgName]
+    }
+
+    override fun findClass(className: String): PsiClassItem? {
+        return classMap[className]
+    }
+
+    open fun findClass(psiClass: PsiClass): PsiClassItem? {
+        val qualifiedName: String = psiClass.qualifiedName ?: psiClass.name!!
+        return classMap[qualifiedName]
+    }
+
+    open fun findOrCreateClass(psiClass: PsiClass): PsiClassItem {
+        val existing = findClass(psiClass)
+        if (existing != null) {
+            return existing
+        }
+
+        var curr = psiClass.containingClass
+        if (curr != null && findClass(curr) == null) {
+            // Make sure we construct outer/top level classes first
+            if (findClass(curr) == null) {
+                while (true) {
+                    val containing = curr?.containingClass
+                    if (containing == null) {
+                        break
+                    } else {
+                        curr = containing
+                    }
+                }
+                curr!!
+                createClass(curr) // this will also create inner classes, which should now be in the map
+                val inner = findClass(psiClass)
+                inner!! // should be there now
+                return inner
+            }
+
+        }
+
+        return existing ?: return createClass(psiClass)
+    }
+
+    fun findClass(psiType: PsiType): PsiClassItem? {
+        if (psiType is PsiClassType) {
+            val cls = psiType.resolve() ?: return null
+            return findOrCreateClass(cls)
+        }
+        return null
+    }
+
+    fun getClassType(cls: PsiClass): PsiClassType = getFactory().createType(cls)
+
+    fun getComment(string: String, parent: PsiElement? = null): PsiDocComment =
+        getFactory().createDocCommentFromText(string, parent)
+
+    fun getType(psiType: PsiType): PsiTypeItem {
+        // Note: We do *not* cache these; it turns out that storing PsiType instances
+        // in a map is bad for performance; it has a very expensive equals operation
+        // for some type comparisons (and we sometimes end up with unexpected results,
+        // e.g. where we fetch an "equals" type from the map but its representation
+        // is slightly different than we intended
+        return PsiTypeItem.create(this, psiType)
+    }
+
+    fun getType(psiClass: PsiClass): PsiTypeItem {
+        return PsiTypeItem.create(this, getFactory().createType(psiClass))
+    }
+
+    private fun getPackageName(clz: PsiClass): String {
+        var top: PsiClass? = clz
+        while (top?.containingClass != null) {
+            top = top.containingClass
+        }
+        top ?: return ""
+
+        val name = top.name
+        val fullName = top.qualifiedName ?: return ""
+
+        return fullName.substring(0, fullName.length - 1 - name!!.length)
+    }
+
+    fun findMethod(method: PsiMethod): PsiMethodItem {
+        val containingClass = method.containingClass
+        val cls = findOrCreateClass(containingClass!!)
+
+        // Ensure initialized/registered via [#registerMethods]
+        if (methodMap[cls] == null) {
+            val map = HashMap<PsiMethod, PsiMethodItem>(40)
+            registerMethods(cls.methods(), map)
+            registerMethods(cls.constructors(), map)
+            methodMap[cls] = map
+        }
+
+        val methods = methodMap[cls]!!
+        val methodItem = methods[method]
+        if (methodItem == null) {
+            // Probably switched psi classes (e.g. used source PsiClass in registry but
+            // found duplicate class in .jar library and we're now pointing to it; in that
+            // case, find the equivalent method by signature
+            val psiClass = cls.psiClass
+            val updatedMethod = psiClass.findMethodBySignature(method, true)
+            val result = methods[updatedMethod!!]
+            if (result == null) {
+                val extra = PsiMethodItem.create(this, cls, updatedMethod)
+                methods.put(method, extra)
+                methods.put(updatedMethod, extra)
+                if (!initializing) {
+                    extra.finishInitialization()
+                }
+
+                return extra
+            }
+            return result
+        }
+
+        return methodItem
+    }
+
+    fun findField(field: PsiField): Item? {
+        val containingClass = field.containingClass ?: return null
+        val cls = findOrCreateClass(containingClass)
+        return cls.findField(field.name)
+    }
+
+    private fun registerMethods(methods: List<MethodItem>, map: MutableMap<PsiMethod, PsiMethodItem>) {
+        for (method in methods) {
+            val psiMethod = (method as PsiMethodItem).psiMethod
+            map[psiMethod] = method
+        }
+    }
+
+    fun createReferenceFromText(s: String, parent: PsiElement? = null): PsiJavaCodeReferenceElement =
+        getFactory().createReferenceFromText(s, parent)
+
+    fun createPsiMethod(s: String, parent: PsiElement? = null): PsiMethod =
+        getFactory().createMethodFromText(s, parent)
+
+    fun createPsiType(s: String, parent: PsiElement? = null): PsiType =
+        getFactory().createTypeFromText(s, parent)
+
+    private fun createPsiAnnotation(s: String, parent: PsiElement? = null): PsiAnnotation =
+        getFactory().createAnnotationFromText(s, parent)
+
+    fun createDocTagFromText(s: String): PsiDocTag = getFactory().createDocTagFromText(s)
+
+    private fun getFactory() = JavaPsiFacade.getElementFactory(project)
+
+    override fun createAnnotation(
+        @Language("JAVA") source: String, context: Item?,
+        mapName: Boolean
+    ): PsiAnnotationItem {
+        val psiAnnotation = createPsiAnnotation(source, context?.psi())
+        return PsiAnnotationItem.create(this, psiAnnotation)
+    }
+
+    override fun supportsDocumentation(): Boolean = true
+
+    override fun toString(): String = description
+
+    override fun filter(filterEmit: Predicate<Item>, filterReference: Predicate<Item>): Codebase =
+        filter(this, filterEmit, filterReference)
+
+    companion object {
+        // This is on the companion object rather than as an instance method to make sure
+        // we don't accidentally call self-methods on the old codebase when we meant the
+        // new: this forces us to be explicit about which codebase we mean
+        fun filter(
+            oldCodebase: PsiBasedCodebase,
+            filterEmit: Predicate<Item>,
+            filterReference: Predicate<Item>
+        ): Codebase {
+            val newCodebase = LockedPsiBasedCodebase("Filtered ${oldCodebase.description}") as PsiBasedCodebase
+            with(newCodebase) {
+                project = oldCodebase.project
+                hiddenPackages = HashMap(oldCodebase.hiddenPackages)
+                packageMap = HashMap(PACKAGE_ESTIMATE)
+                packageClasses = HashMap(PACKAGE_ESTIMATE)
+                packageClasses[""] = ArrayList()
+                methodMap = HashMap(METHOD_ESTIMATE)
+                initializing = true
+                original = oldCodebase
+                units = oldCodebase.units
+            }
+
+            val oldToNew: BiMap<PsiClassItem, PsiClassItem> = HashBiMap.create(30000)
+
+            val newPackages = mutableListOf<PsiPackageItem>()
+
+            val oldPackages = oldCodebase.packageMap.values
+            for (pkg in oldPackages) {
+                if (pkg.hidden) {
+                    continue
+                }
+                var currentPackage: PsiPackageItem? = null
+
+                for (cls in pkg.topLevelClasses()) {
+                    if (cls.isFromClassPath()) {
+                        continue
+                    }
+                    val classFilter = FilteredClassView(cls as PsiClassItem, filterEmit, filterReference)
+                    if (classFilter.emit()) {
+                        val newPackage = currentPackage ?: run {
+                            val newPackage = PsiPackageItem.create(newCodebase, pkg)
+                            currentPackage = newPackage
+                            newPackages.add(newPackage)
+                            newCodebase.packageMap[newPackage.qualifiedName()] = newPackage
+                            newPackage
+                        }
+
+                        // Bottom-up copy
+                        val newClass = classFilter.create(newCodebase)
+
+                        // Register it and all inner classes in the class map
+                        for (c in newClass.allInnerClasses(includeSelf = true)) {
+                            newCodebase.classMap[c.qualifiedName()] = c as PsiClassItem
+                        }
+
+                        newPackage.addClass(newClass) // (inner classes are not registered in the package)
+
+                        oldToNew.put(cls, newClass)
+                        newClass.containingPackage = newPackage
+                    }
+                }
+            }
+
+            // Initialize super classes and super methods
+            for (cls in newCodebase.classMap.values) {
+                val originalClass = cls.source!! // should be set here during construction
+                val prevSuperClass = originalClass.filteredSuperClassType(filterReference)
+                val curr = prevSuperClass?.asClass()
+                if (curr != null) {
+                    val superClassName = curr.qualifiedName()
+                    val publicSuperClass: PsiClassItem? = newCodebase.classMap[superClassName]
+                    cls.setSuperClass(
+                        if (publicSuperClass == null) {
+                            if (curr.isFromClassPath() && options.allowReferencingUnknownClasses) {
+                                curr
+                            } else {
+                                reporter.report(
+                                    Errors.HIDDEN_SUPERCLASS, originalClass.psiClass,
+                                    "$cls has a super class " +
+                                            "that is excluded via filters: $superClassName"
+                                )
+                                null
+                            }
+                        } else {
+                            newCodebase.classMap[superClassName] = publicSuperClass
+                            publicSuperClass
+                        },
+                        PsiTypeItem.create(newCodebase, prevSuperClass as PsiTypeItem)
+                    )
+                } else {
+                    // typically java.lang.Object
+                    cls.setSuperClass(null, null)
+                }
+
+                val psiClass = cls.psiClass
+
+                val filtered = originalClass.filteredInterfaceTypes(filterReference)
+                if (filtered.isEmpty()) {
+                    cls.setInterfaces(emptyList())
+                } else {
+                    val interfaceTypeList = mutableListOf<PsiTypeItem>()
+                    for (type in filtered) {
+                        interfaceTypeList.add(PsiTypeItem.create(newCodebase, type as PsiTypeItem))
+                    }
+                    cls.setInterfaces(interfaceTypeList)
+                }
+
+                val oldCls = oldCodebase.findOrCreateClass(cls.psiClass)
+                val oldDefaultConstructor = oldCls.defaultConstructor
+                if (oldDefaultConstructor != null) {
+                    val newConstructor = cls.findConstructor(oldDefaultConstructor) as PsiConstructorItem?
+                    if (newConstructor != null) {
+                        cls.defaultConstructor = newConstructor
+                    } else {
+                        // Constructor picked before that isn't available here: recreate it
+                        val recreated = cls.createDefaultConstructor()
+
+                        recreated.mutableModifiers().setPackagePrivate(true)
+                        cls.defaultConstructor = recreated
+                    }
+                }
+
+                val constructors = cls.constructors().asSequence()
+                val methods = cls.methods().asSequence()
+                val allMethods = methods.plus(constructors)
+
+                // Super constructors
+                for (method in constructors) {
+                    val original = method.source as PsiConstructorItem
+
+                    val originalSuperConstructor = original.superConstructor
+                    val superConstructor =
+                        if (originalSuperConstructor != null && filterReference.test(originalSuperConstructor)) {
+                            originalSuperConstructor
+                        } else {
+                            original.findDelegate(filterReference, true)
+                        }
+
+                    if (superConstructor != null) {
+                        val superConstructorClass =
+                            newCodebase.classMap[superConstructor.containingClass().qualifiedName()]
+                        if (superConstructorClass == null) {
+                            // This class is not in the filtered codebase
+                            if (superConstructor.isFromClassPath() && options.allowReferencingUnknownClasses) {
+                                method.superConstructor = superConstructor
+                                method.setSuperMethods(listOf(superConstructor))
+                            } else {
+                                reporter.report(
+                                    Errors.HIDDEN_SUPERCLASS, psiClass, "$method has a super method " +
+                                            "in a class that is excluded via filters: " +
+                                            "${superConstructor.containingClass().qualifiedName()} "
+                                )
+                            }
+                        } else {
+                            // Find corresponding super method
+                            val newSuperConstructor = superConstructorClass.findMethod(superConstructor)
+                            if (newSuperConstructor == null) {
+                                reporter.report(
+                                    Errors.HIDDEN_SUPERCLASS, psiClass, "$method has a super method " +
+                                            "in a class that is not matched via filters: " +
+                                            "${superConstructor.containingClass().qualifiedName()} "
+                                )
+                            } else {
+                                val constructorItem = newSuperConstructor as PsiConstructorItem
+                                method.superConstructor = constructorItem
+                                method.setSuperMethods(listOf(constructorItem))
+                            }
+                        }
+                    } else {
+                        method.setSuperMethods(emptyList())
+                    }
+                }
+
+                // Super methods
+                for (method in methods) {
+                    val original = method.source!! // should be set here
+                    val list = mutableListOf<MethodItem>()
+                    val superMethods = ArrayDeque<MethodItem>()
+                    superMethods.addAll(original.superMethods())
+                    while (!superMethods.isEmpty()) {
+                        val superMethod = superMethods.removeFirst()
+                        if (filterReference.test(superMethod)) {
+                            // Find corresponding method in the new filtered codebase
+                            val superMethodClass = newCodebase.classMap[superMethod.containingClass().qualifiedName()]
+                            if (superMethodClass == null) {
+                                // This class is not in the filtered codebase
+                                if (superMethod.isFromClassPath() && options.allowReferencingUnknownClasses) {
+                                    list.add(superMethod)
+                                } else {
+                                    reporter.report(
+                                        Errors.HIDDEN_SUPERCLASS, psiClass, "$method has a super method " +
+                                                "in a class that is excluded via filters: " +
+                                                "${superMethod.containingClass().qualifiedName()} "
+                                    )
+                                }
+                            } else {
+                                // Find corresponding super method
+                                val newSuperMethod = superMethodClass.findMethod(superMethod)
+                                if (newSuperMethod == null) {
+                                    reporter.report(
+                                        Errors.HIDDEN_SUPERCLASS, psiClass, "$method has a super method " +
+                                                "in a class that is not matched via filters: " +
+                                                "${superMethod.containingClass().qualifiedName()} "
+                                    )
+                                } else {
+                                    list.add(newSuperMethod)
+                                }
+                            }
+                        } else {
+                            // Process its parents instead
+                            superMethods.addAll(superMethod.superMethods())
+                        }
+                    }
+                    method.setSuperMethods(list)
+                }
+
+                // Methods and constructors: initialize throws lists
+                for (method in allMethods) {
+                    val original = method.source!! // should be set here
+
+                    val throwsTypes: List<PsiClassItem> = if (original.throwsTypes().isNotEmpty()) {
+                        val list = ArrayList<PsiClassItem>()
+
+                        original.filteredThrowsTypes(filterReference).forEach {
+                            val newCls = newCodebase.classMap[it.qualifiedName()]
+                            if (newCls == null) {
+                                if (it.isFromClassPath() && options.allowReferencingUnknownClasses) {
+                                    list.add(it as PsiClassItem)
+                                } else {
+                                    reporter.report(
+                                        Errors.HIDDEN_SUPERCLASS, psiClass, "$newCls has a throws class " +
+                                                "that is excluded via filters: ${it.qualifiedName()}"
+                                    )
+                                }
+                            } else {
+                                list.add(newCls)
+                            }
+                        }
+                        list
+                    } else {
+                        emptyList()
+                    }
+                    method.setThrowsTypes(throwsTypes)
+
+                    method.source = null
+                }
+
+                cls.source = null
+            }
+
+            val pkg: PsiPackageItem? = newCodebase.findPackage("") ?: run {
+                val psiPackage = JavaPsiFacade.getInstance(newCodebase.project).findPackage("")
+                if (psiPackage != null) {
+                    PsiPackageItem.create(newCodebase, psiPackage, null)
+                } else {
+                    null
+                }
+            }
+            pkg?.let {
+                newCodebase.emptyPackage = it
+                newCodebase.packageMap[""] = it
+            }
+
+            newCodebase.addParentPackages(newCodebase.packageMap.values)
+
+            newCodebase.initializing = false
+
+            return newCodebase
+        }
+    }
+
+    fun registerClass(cls: PsiClassItem) {
+        assert(classMap[cls.qualifiedName()] == null || classMap[cls.qualifiedName()] == cls)
+
+        classMap[cls.qualifiedName()] = cls
+    }
+}
+
+class FilteredClassView(
+    val cls: PsiClassItem,
+    private val filterEmit: Predicate<Item>,
+    private val filterReference: Predicate<Item>
+) {
+    val innerClasses: Sequence<FilteredClassView>
+    val constructors: Sequence<MethodItem>
+    val methods: Sequence<MethodItem>
+    val fields: Sequence<FieldItem>
+
+    init {
+        constructors = cls.constructors().asSequence().filter { filterEmit.test(it) }
+        methods = cls.methods().asSequence().filter { filterEmit.test(it) }
+        //fields = cls.fields().asSequence().filter { filterEmit.test(it) }
+
+        fields = cls.filteredFields(filterEmit).asSequence()
+        innerClasses = cls.innerClasses()
+            .asSequence()
+            .filterIsInstance(PsiClassItem::class.java)
+            .map { FilteredClassView(it, filterEmit, filterReference) }
+    }
+
+    fun create(codebase: PsiBasedCodebase): PsiClassItem {
+        return PsiClassItem.create(codebase, this)
+    }
+
+    /** Will this class emit anything? */
+    fun emit(): Boolean {
+        val emit = emitClass()
+        if (emit) {
+            return true
+        }
+
+        return innerClasses.any { it.emit() }
+    }
+
+    /** Does the body of this class (everything other than the inner classes) emit anything? */
+    private fun emitClass(): Boolean {
+        val classEmpty = (constructors.none() && methods.none() && fields.none())
+        return if (filterEmit.test(cls)) {
+            true
+        } else if (!classEmpty) {
+            filterReference.test(cls)
+        } else {
+            false
+        }
+    }
+}
+
+class LockedPsiBasedCodebase(description: String = "Unknown") : PsiBasedCodebase(description) {
+    // Not yet locked
+    //override fun findClass(psiClass: PsiClass): PsiClassItem {
+    //    val qualifiedName: String = psiClass.qualifiedName ?: psiClass.name!!
+    //    return classMap[qualifiedName] ?: error("Attempted to register ${psiClass.name} in locked codebase")
+    //}
+}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/model/psi/PsiClassItem.kt b/src/main/java/com/android/tools/metalava/model/psi/PsiClassItem.kt
new file mode 100644
index 0000000..f06e9f9
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/model/psi/PsiClassItem.kt
@@ -0,0 +1,764 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava.model.psi
+
+import com.android.tools.metalava.compatibility
+import com.android.tools.metalava.model.ClassItem
+import com.android.tools.metalava.model.CompilationUnit
+import com.android.tools.metalava.model.ConstructorItem
+import com.android.tools.metalava.model.FieldItem
+import com.android.tools.metalava.model.MethodItem
+import com.android.tools.metalava.model.PackageItem
+import com.android.tools.metalava.model.TypeItem
+import com.google.common.base.Splitter
+import com.intellij.lang.jvm.types.JvmReferenceType
+import com.intellij.psi.PsiClass
+import com.intellij.psi.PsiClassType
+import com.intellij.psi.PsiComment
+import com.intellij.psi.PsiCompiledFile
+import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiJavaFile
+import com.intellij.psi.PsiModifier
+import com.intellij.psi.PsiModifierListOwner
+import com.intellij.psi.PsiType
+import com.intellij.psi.PsiTypeParameter
+import com.intellij.psi.PsiWhiteSpace
+import com.intellij.psi.impl.source.PsiClassReferenceType
+import com.intellij.psi.util.PsiUtil
+import org.jetbrains.kotlin.kdoc.psi.api.KDoc
+import org.jetbrains.kotlin.psi.KtFile
+import org.jetbrains.kotlin.psi.psiUtil.startOffset
+
+class PsiClassItem(
+    override val codebase: PsiBasedCodebase,
+    val psiClass: PsiClass,
+    private val name: String,
+    private val fullName: String,
+    private val qualifiedName: String,
+    private val hasImplicitDefaultConstructor: Boolean,
+    private val classType: ClassType,
+    modifiers: PsiModifierItem,
+    documentation: String
+) :
+    PsiItem(
+        codebase = codebase,
+        modifiers = modifiers,
+        documentation = documentation,
+        element = psiClass
+    ), ClassItem {
+    lateinit var containingPackage: PsiPackageItem
+
+    override fun containingPackage(): PackageItem = containingClass?.containingPackage() ?: containingPackage
+    override fun simpleName(): String = name
+    override fun fullName(): String = fullName
+    override fun qualifiedName(): String = qualifiedName
+    override fun isInterface(): Boolean = classType == ClassType.INTERFACE
+    override fun isAnnotationType(): Boolean = classType == ClassType.ANNOTATION_TYPE
+    override fun isEnum(): Boolean = classType == ClassType.ENUM
+    override fun hasImplicitDefaultConstructor(): Boolean = hasImplicitDefaultConstructor
+
+    private var superClass: ClassItem? = null
+    private var superClassType: TypeItem? = null
+    override fun superClass(): ClassItem? = superClass
+    override fun superClassType(): TypeItem? = superClassType
+
+    override fun setSuperClass(superClass: ClassItem?, superClassType: TypeItem?) {
+        this.superClass = superClass
+        this.superClassType = superClassType
+    }
+
+    override var defaultConstructor: ConstructorItem? = null
+
+    private var containingClass: PsiClassItem? = null
+    override fun containingClass(): PsiClassItem? = containingClass
+    fun setContainingClass(containingClass: ClassItem?) {
+        this.containingClass = containingClass as PsiClassItem?
+    }
+
+    // TODO: Come up with a better scheme for how to compute this
+    override var included: Boolean = true
+
+    override var hasPrivateConstructor: Boolean = false
+
+    override fun interfaceTypes(): List<TypeItem> = interfaceTypes
+
+    override fun setInterfaceTypes(interfaceTypes: List<TypeItem>) {
+        @Suppress("UNCHECKED_CAST")
+        setInterfaces(interfaceTypes as List<PsiTypeItem>)
+    }
+
+    fun setInterfaces(interfaceTypes: List<PsiTypeItem>) {
+        this.interfaceTypes = interfaceTypes
+    }
+
+    private var allInterfaces: List<ClassItem>? = null
+
+    override fun allInterfaces(): Sequence<ClassItem> {
+        if (allInterfaces == null) {
+            val classes = mutableSetOf<PsiClass>()
+            var curr: PsiClass? = psiClass
+            while (curr != null) {
+                if (curr.isInterface && !classes.contains(curr)) {
+                    classes.add(curr)
+                }
+                addInterfaces(classes, curr.interfaces)
+                curr = curr.superClass
+            }
+            val result = mutableListOf<ClassItem>()
+            for (cls in classes) {
+                val item = codebase.findOrCreateClass(cls)
+                result.add(item)
+            }
+
+            allInterfaces = result
+        }
+
+        return allInterfaces!!.asSequence()
+    }
+
+    private fun addInterfaces(result: MutableSet<PsiClass>, interfaces: Array<out PsiClass>) {
+        for (itf in interfaces) {
+            if (itf.isInterface && !result.contains(itf)) {
+                result.add(itf)
+                addInterfaces(result, itf.interfaces)
+                val superClass = itf.superClass
+                if (superClass != null) {
+                    addInterfaces(result, arrayOf(superClass))
+                }
+            }
+        }
+    }
+
+    private lateinit var innerClasses: List<PsiClassItem>
+    private lateinit var interfaceTypes: List<TypeItem>
+    private lateinit var constructors: List<PsiConstructorItem>
+    private lateinit var methods: List<PsiMethodItem>
+    private lateinit var fields: List<FieldItem>
+
+    /**
+     * If this item was created by filtering down a different codebase, this temporarily
+     * points to the original item during construction. This is used to let us initialize
+     * for example throws lists later, when all classes in the codebase have been
+     * initialized.
+     */
+    internal var source: PsiClassItem? = null
+
+    override fun innerClasses(): List<PsiClassItem> = innerClasses
+    override fun constructors(): List<PsiConstructorItem> = constructors
+    override fun methods(): List<PsiMethodItem> = methods
+    override fun fields(): List<FieldItem> = fields
+
+    override fun toType(): TypeItem {
+        return PsiTypeItem.create(codebase, codebase.getClassType(psiClass))
+    }
+
+    override fun hasTypeVariables(): Boolean = psiClass.hasTypeParameters()
+
+    override fun typeParameterList(): String? {
+        return PsiTypeItem.typeParameterList(psiClass.typeParameterList)
+    }
+
+    override fun typeArgumentClasses(): List<ClassItem> {
+        return PsiTypeItem.typeParameterClasses(
+            codebase,
+            psiClass.typeParameterList
+        )
+    }
+
+    override fun typeParameterNames(): List<String> {
+        if (!psiClass.hasTypeParameters()) {
+            return emptyList()
+        }
+
+        val typeParameters = psiClass.typeParameters
+        val list = mutableListOf<String>()
+        for (parameter in typeParameters) {
+            list.add(parameter.name ?: continue)
+        }
+
+        return list
+    }
+
+    override val isTypeParameter: Boolean
+        get() = psiClass is PsiTypeParameter
+
+    override fun getCompilationUnit(): CompilationUnit? {
+        if (isInnerClass()) {
+            return null
+        }
+
+        val containingFile = psiClass.containingFile ?: return null
+        if (containingFile is PsiCompiledFile) {
+            return null
+        }
+
+        return object : CompilationUnit(containingFile) {
+            override fun getHeaderComments(): String? {
+                // https://youtrack.jetbrains.com/issue/KT-22135
+                if (file is PsiJavaFile) {
+                    val pkg = file.packageStatement ?: return null
+                    return file.text.substring(0, pkg.startOffset)
+                } else if (file is KtFile) {
+                    var curr: PsiElement? = file.firstChild
+                    var comment: String? = null
+                    while (curr != null) {
+                        if (curr is PsiComment || curr is KDoc) {
+                            val text = curr.text
+                            comment = if (comment != null) {
+                                comment + "\n" + text
+                            } else {
+                                text
+                            }
+                        } else if (curr !is PsiWhiteSpace) {
+                            break
+                        }
+                        curr = curr.nextSibling
+                    }
+                    return comment
+                }
+
+                return super.getHeaderComments()
+            }
+        }
+    }
+
+    fun findMethod(template: MethodItem): MethodItem? {
+        if (template.isConstructor()) {
+            return findConstructor(template as ConstructorItem)
+        }
+
+        methods().asSequence()
+            .filter { it.matches(template) }
+            .forEach { return it }
+        return null
+    }
+
+    fun findConstructor(template: ConstructorItem): ConstructorItem? {
+        constructors().asSequence()
+            .filter { it.matches(template) }
+            .forEach { return it }
+        return null
+    }
+
+    override fun findMethod(methodName: String, parameters: String): MethodItem? {
+        if (methodName == simpleName()) {
+            // Constructor
+            constructors()
+                .filter { parametersMatch(it, parameters) }
+                .forEach { return it }
+        } else {
+            methods()
+                .filter { it.name() == methodName && parametersMatch(it, parameters) }
+                .forEach { return it }
+        }
+
+        return null
+    }
+
+    private fun parametersMatch(method: MethodItem, description: String): Boolean {
+        val parameterStrings = Splitter.on(",").trimResults().omitEmptyStrings().splitToList(description)
+        val parameters = method.parameters()
+        if (parameters.size != parameterStrings.size) {
+            return false
+        }
+        for (i in 0 until parameters.size) {
+            var parameterString = parameterStrings[i]
+            val index = parameterString.indexOf('<')
+            if (index != -1) {
+                parameterString = parameterString.substring(0, index)
+            }
+            val parameter = parameters[i].type().toErasedTypeString()
+            if (parameter != parameterString) {
+                return false
+            }
+        }
+
+        return true
+    }
+
+    override fun findField(fieldName: String): FieldItem? {
+        return fields().firstOrNull { it.name() == fieldName }
+    }
+
+    override fun finishInitialization() {
+        super.finishInitialization()
+
+        for (method in methods) {
+            method.finishInitialization()
+        }
+        for (method in constructors) {
+            method.finishInitialization()
+        }
+        for (field in fields) {
+            // There may be non-Psi fields here later (thanks to addField) but not during construction
+            (field as PsiFieldItem).finishInitialization()
+        }
+        for (inner in innerClasses) {
+            inner.finishInitialization()
+        }
+
+        val extendsListTypes = psiClass.extendsListTypes
+        if (!extendsListTypes.isEmpty()) {
+            val type = PsiTypeItem.create(codebase, extendsListTypes[0])
+            this.superClassType = type
+            this.superClass = type.asClass()
+        } else {
+            val superType = psiClass.superClassType
+            if (superType is PsiType) {
+                this.superClassType = PsiTypeItem.create(codebase, superType)
+                this.superClass = this.superClassType?.asClass()
+            }
+        }
+
+        // Add interfaces. If this class is an interface, it can implement both
+        // classes from the extends clause and from the implements clause.
+        val interfaces = psiClass.implementsListTypes
+        setInterfaces(if (interfaces.isEmpty() && extendsListTypes.size <= 1) {
+            emptyList()
+        } else {
+            val result = ArrayList<PsiTypeItem>(interfaces.size + extendsListTypes.size - 1)
+            val create: (PsiClassType) -> PsiTypeItem = {
+                val type = PsiTypeItem.create(codebase, it)
+                type.asClass() // ensure that we initialize classes eagerly too such that they're registered etc
+                type
+            }
+            (1 until extendsListTypes.size).mapTo(result) { create(extendsListTypes[it]) }
+            interfaces.mapTo(result) { create(it) }
+            result
+        })
+    }
+
+    override fun mapTypeVariables(target: ClassItem, reverse: Boolean): Map<String, String> {
+        val targetPsi = target.psi() as PsiClass
+        val maps = mapTypeVariablesToSuperclass(
+            psiClass, targetPsi, considerSuperClasses = true,
+            considerInterfaces = targetPsi.isInterface
+        ) ?: return emptyMap()
+
+        if (maps.isEmpty()) {
+            return emptyMap()
+        }
+
+        if (maps.size == 1) {
+            return maps[0]
+        }
+
+        val first = maps[0]
+        val flattened = mutableMapOf<String, String>()
+        for (key in first.keys) {
+            var variable: String? = key
+            for (map in maps) {
+                val value = map[variable]
+                variable = value
+                if (value == null) {
+                    break
+                } else {
+                    flattened.put(key, value)
+                }
+            }
+        }
+        return flattened
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) {
+            return true
+        }
+        return other is ClassItem && qualifiedName == other.qualifiedName()
+    }
+
+    /**
+     * Creates a constructor in this class
+     */
+    override fun createDefaultConstructor(): ConstructorItem {
+        return PsiConstructorItem.createDefaultConstructor(codebase, this, psiClass)
+    }
+
+    override fun createMethod(template: MethodItem): MethodItem {
+        val method = template as PsiMethodItem
+
+        val replacementMap = mapTypeVariables(template.containingClass(), reverse = true)
+
+        val newMethod: PsiMethodItem
+        if (replacementMap.isEmpty()) {
+            newMethod = PsiMethodItem.create(codebase, this, method)
+        } else {
+            val stub = method.toStub(replacementMap)
+            val psiMethod = codebase.createPsiMethod(stub, psiClass)
+            newMethod = PsiMethodItem.create(codebase, this, psiMethod)
+            newMethod.inheritedInterfaceMethod = method.inheritedInterfaceMethod
+            newMethod.documentation = method.documentation
+        }
+
+        if (template.throwsTypes().isEmpty()) {
+            newMethod.setThrowsTypes(emptyList())
+        } else {
+            val throwsTypes = mutableListOf<ClassItem>()
+            for (type in template.throwsTypes()) {
+                if (type.codebase === codebase) {
+                    throwsTypes.add(type)
+                } else {
+                    throwsTypes.add(codebase.findOrCreateClass(((type as PsiClassItem).psiClass)))
+                }
+            }
+            newMethod.setThrowsTypes(throwsTypes)
+        }
+
+        return newMethod
+    }
+
+    override fun addMethod(method: MethodItem) {
+        (methods as MutableList<PsiMethodItem>).add(method as PsiMethodItem)
+    }
+
+    override fun hashCode(): Int = qualifiedName.hashCode()
+
+    override fun toString(): String = "class ${qualifiedName()}"
+
+    companion object {
+        fun create(codebase: PsiBasedCodebase, psiClass: PsiClass): PsiClassItem {
+            val simpleName = psiClass.name!!
+            val fullName = computeFullClassName(psiClass)
+            val qualifiedName = psiClass.qualifiedName ?: simpleName
+            val hasImplicitDefaultConstructor = hasImplicitDefaultConstructor(psiClass)
+            val classType = ClassType.getClassType(psiClass)
+
+            val commentText = PsiItem.javadoc(psiClass)
+            val modifiers = modifiers(codebase, psiClass, commentText)
+            val item = PsiClassItem(
+                codebase = codebase,
+                psiClass = psiClass,
+                name = simpleName,
+                fullName = fullName,
+                qualifiedName = qualifiedName,
+                classType = classType,
+                hasImplicitDefaultConstructor = hasImplicitDefaultConstructor,
+                documentation = commentText,
+                modifiers = modifiers
+            )
+            codebase.registerClass(item)
+            item.modifiers.setOwner(item)
+
+            // Construct the children
+            val psiMethods = psiClass.methods
+            val methods: MutableList<PsiMethodItem> = ArrayList(psiMethods.size)
+
+            if (classType == ClassType.ENUM) {
+                addEnumMethods(codebase, item, psiClass, methods)
+            }
+
+            val constructors: MutableList<PsiConstructorItem> = ArrayList(5)
+            for (psiMethod in psiMethods) {
+                if (psiMethod.isPrivate() || psiMethod.isPackagePrivate()) {
+                    item.hasPrivateConstructor = true
+                }
+                if (psiMethod.isConstructor) {
+                    val constructor = PsiConstructorItem.create(codebase, item, psiMethod)
+                    constructors.add(constructor)
+                } else {
+                    val method = PsiMethodItem.create(codebase, item, psiMethod)
+                    methods.add(method)
+                }
+            }
+
+            if (hasImplicitDefaultConstructor) {
+                assert(constructors.isEmpty())
+                constructors.add(PsiConstructorItem.createDefaultConstructor(codebase, item, psiClass))
+            }
+
+            val fields: MutableList<FieldItem> = mutableListOf()
+            val psiFields = psiClass.fields
+            if (!psiFields.isEmpty()) {
+                psiFields.asSequence()
+                    .mapTo(fields) {
+                        PsiFieldItem.create(codebase, item, it)
+                    }
+            }
+
+            if (classType == ClassType.INTERFACE) {
+                // All members are implicitly public, fields are implicitly static, non-static methods are abstract
+                for (method in methods) {
+                    method.mutableModifiers().setPublic(true)
+                }
+                for (method in fields) {
+                    val m = method.mutableModifiers()
+                    m.setPublic(true)
+                    m.setStatic(true)
+                }
+            }
+
+            item.constructors = constructors
+            item.methods = methods
+            item.fields = fields
+
+            val psiInnerClasses = psiClass.innerClasses
+            item.innerClasses = if (psiInnerClasses.isEmpty()) {
+                emptyList()
+            } else {
+                val result = psiInnerClasses.asSequence()
+                    .map {
+                        val inner = codebase.findOrCreateClass(it)
+                        inner.containingClass = item
+                        inner
+                    }
+                    .toMutableList()
+                result
+            }
+
+            return item
+        }
+
+        fun create(codebase: PsiBasedCodebase, classFilter: FilteredClassView): PsiClassItem {
+            val original = classFilter.cls
+
+            val newClass = PsiClassItem(
+                codebase = codebase,
+                psiClass = original.psiClass,
+                name = original.name,
+                fullName = original.fullName,
+                qualifiedName = original.qualifiedName,
+                classType = original.classType,
+                hasImplicitDefaultConstructor = original.hasImplicitDefaultConstructor,
+                documentation = original.documentation,
+                modifiers = PsiModifierItem.create(codebase, original.modifiers)
+            )
+
+            newClass.modifiers.setOwner(newClass)
+            codebase.registerClass(newClass)
+            newClass.source = original
+
+            newClass.constructors = classFilter.constructors.map {
+                PsiConstructorItem.create(codebase, newClass, it as PsiConstructorItem)
+            }.toMutableList()
+
+            newClass.methods = classFilter.methods.map {
+                PsiMethodItem.create(codebase, newClass, it as PsiMethodItem)
+            }.toMutableList()
+
+
+            newClass.fields = classFilter.fields.asSequence()
+                // Preserve sorting order for enums
+                .sortedBy { it.sortingRank }.map {
+                PsiFieldItem.create(codebase, newClass, it as PsiFieldItem)
+            }.toMutableList()
+
+
+            newClass.innerClasses = classFilter.innerClasses.map {
+                val newInnerClass = codebase.findClass(it.cls.qualifiedName) ?: it.create(codebase)
+                newInnerClass.containingClass = newClass
+                codebase.registerClass(newInnerClass)
+                newInnerClass
+            }.toMutableList()
+
+            newClass.hasPrivateConstructor = classFilter.cls.hasPrivateConstructor
+
+            return newClass
+        }
+
+        private fun addEnumMethods(
+            codebase: PsiBasedCodebase,
+            classItem: PsiClassItem,
+            psiClass: PsiClass,
+            result: MutableList<PsiMethodItem>
+        ) {
+            // Add these two methods as overrides into the API; this isn't necessary but is done in the old
+            // API generator
+            //    method public static android.graphics.ColorSpace.Adaptation valueOf(java.lang.String);
+            //    method public static final android.graphics.ColorSpace.Adaptation[] values();
+
+            if (compatibility.defaultAnnotationMethods) {
+                // TODO: Skip if we already have these methods here (but that shouldn't happen; nobody would
+                // type this by hand)
+                addEnumMethod(
+                    codebase, classItem,
+                    psiClass, result,
+                    "public static ${psiClass.qualifiedName} valueOf(java.lang.String s) { return null; }"
+                )
+                addEnumMethod(
+                    codebase, classItem,
+                    psiClass, result,
+                    "public static final ${psiClass.qualifiedName}[] values() { return null; }"
+                )
+            }
+        }
+
+        private fun addEnumMethod(
+            codebase: PsiBasedCodebase,
+            classItem: PsiClassItem,
+            psiClass: PsiClass,
+            result: MutableList<PsiMethodItem>, source: String
+        ) {
+            val psiMethod = codebase.createPsiMethod(source, psiClass)
+            result.add(PsiMethodItem.create(codebase, classItem, psiMethod))
+        }
+
+        /**
+         * Computes the "full" class name; this is not the qualified class name (e.g. with package)
+         * but for an inner class it includes all the outer classes
+         */
+        private fun computeFullClassName(cls: PsiClass): String {
+            if (cls.containingClass == null) {
+                val name = cls.name
+                return name!!
+            } else {
+                val list = mutableListOf<String>()
+                var curr: PsiClass? = cls
+                while (curr != null) {
+                    val name = curr.name
+                    curr = if (name != null) {
+                        list.add(name)
+                        curr.containingClass
+                    } else {
+                        break
+
+                    }
+                }
+                return list.asReversed().asSequence().joinToString(separator = ".") { it }
+            }
+        }
+
+        private fun hasImplicitDefaultConstructor(psiClass: PsiClass): Boolean {
+            val constructors = psiClass.constructors
+            if (constructors.isEmpty() && !psiClass.isInterface && !psiClass.isAnnotationType && !psiClass.isEnum) {
+                if (PsiUtil.hasDefaultConstructor(psiClass)) {
+                    return true
+                }
+
+                // The above method isn't always right; for example, for the ContactsContract.Presence class
+                // in the framework, which looks like this:
+                //    @Deprecated
+                //    public static final class Presence extends StatusUpdates {
+                //    }
+                // javac makes a default constructor:
+                //    public final class android.provider.ContactsContract$Presence extends android.provider.ContactsContract$StatusUpdates {
+                //        public android.provider.ContactsContract$Presence();
+                //    }
+                // but the above method returns false. So add some of our own heuristics:
+                if (psiClass.hasModifierProperty(PsiModifier.FINAL) && !psiClass.hasModifierProperty(
+                        PsiModifier.ABSTRACT
+                    ) &&
+                    psiClass.hasModifierProperty(PsiModifier.PUBLIC)
+                ) {
+                    return true
+                }
+            }
+
+            return false
+        }
+
+        fun mapTypeVariablesToSuperclass(
+            psiClass: PsiClass,
+            targetClass: PsiClass,
+            considerSuperClasses: Boolean = true,
+            considerInterfaces: Boolean = psiClass.isInterface
+        ): MutableList<Map<String, String>>? {
+            // TODO: Prune search if type doesn't have type arguments!
+            if (considerSuperClasses) {
+                val list = mapTypeVariablesToSuperclass(
+                    psiClass.superClassType, targetClass,
+                    considerSuperClasses, considerInterfaces
+                )
+                if (list != null) {
+                    return list
+                }
+            }
+
+            if (considerInterfaces) {
+                for (interfaceType in psiClass.interfaceTypes) {
+                    val list = mapTypeVariablesToSuperclass(
+                        interfaceType, targetClass,
+                        considerSuperClasses, considerInterfaces
+                    )
+                    if (list != null) {
+                        return list
+                    }
+                }
+            }
+
+            return null
+        }
+
+        fun mapTypeVariablesToSuperclass(
+            type: JvmReferenceType?,
+            targetClass: PsiClass,
+            considerSuperClasses: Boolean = true,
+            considerInterfaces: Boolean = true
+        ): MutableList<Map<String, String>>? {
+            // TODO: Prune search if type doesn't have type arguments!
+            val superType = type as? PsiClassReferenceType
+            val superClass = superType?.resolve()
+            if (superClass != null) {
+                if (superClass == targetClass) {
+                    val map = mapTypeVariablesToSuperclass(superType)
+                    if (map != null) {
+                        return mutableListOf(map)
+                    } else {
+                        return null
+                    }
+                } else {
+                    val list = mapTypeVariablesToSuperclass(
+                        superClass, targetClass, considerSuperClasses,
+                        considerInterfaces
+                    )
+                    if (list != null) {
+                        val map = mapTypeVariablesToSuperclass(superType)
+                        if (map != null) {
+                            list.add(map)
+                        }
+                        return list
+                    }
+                }
+            }
+
+            return null
+        }
+
+        fun mapTypeVariablesToSuperclass(superType: PsiClassReferenceType?): Map<String, String>? {
+            superType ?: return null
+
+            val map = mutableMapOf<String, String>()
+            val superClass = superType.resolve()
+            if (superClass != null && superType.hasParameters()) {
+                val superTypeParameters = superClass.typeParameters
+                superType.parameters.forEachIndexed { index, parameter ->
+                    if (parameter is PsiClassReferenceType) {
+                        val parameterClass = parameter.resolve()
+                        if (parameterClass != null) {
+                            val parameterName = parameterClass.qualifiedName ?: parameterClass.name ?: parameter.name
+                            if (index < superTypeParameters.size) {
+                                val superTypeParameter = superTypeParameters[index]
+                                val superTypeName = superTypeParameter.qualifiedName ?: superTypeParameter.name
+                                if (superTypeName != null) {
+                                    map.put(superTypeName, parameterName)
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+
+            return map
+        }
+    }
+}
+
+fun PsiModifierListOwner.isPrivate(): Boolean = modifierList?.hasExplicitModifier(PsiModifier.PRIVATE) == true
+fun PsiModifierListOwner.isPackagePrivate(): Boolean {
+    val modifiers = modifierList ?: return false
+    return !(modifiers.hasModifierProperty(PsiModifier.PUBLIC) ||
+            modifiers.hasModifierProperty(PsiModifier.PROTECTED))
+}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/model/psi/PsiConstructorItem.kt b/src/main/java/com/android/tools/metalava/model/psi/PsiConstructorItem.kt
new file mode 100644
index 0000000..f0db643
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/model/psi/PsiConstructorItem.kt
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava.model.psi
+
+import com.android.tools.metalava.model.ConstructorItem
+import com.android.tools.metalava.model.Item
+import com.android.tools.metalava.model.MethodItem
+import com.intellij.psi.JavaPsiFacade
+import com.intellij.psi.PsiClass
+import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiExpressionStatement
+import com.intellij.psi.PsiKeyword
+import com.intellij.psi.PsiMethod
+import com.intellij.psi.PsiMethodCallExpression
+import com.intellij.psi.PsiWhiteSpace
+import java.util.function.Predicate
+
+class PsiConstructorItem(
+    codebase: PsiBasedCodebase,
+    psiMethod: PsiMethod,
+    containingClass: PsiClassItem,
+    name: String,
+    modifiers: PsiModifierItem,
+    documentation: String,
+    parameters: List<PsiParameterItem>,
+    returnType: PsiTypeItem,
+    private val implicitConstructor: Boolean = false
+) :
+    PsiMethodItem(
+        codebase = codebase,
+        modifiers = modifiers,
+        documentation = documentation,
+        psiMethod = psiMethod,
+        containingClass = containingClass,
+        name = name,
+        returnType = returnType,
+        parameters = parameters
+    ), ConstructorItem {
+
+    init {
+        if (implicitConstructor) {
+            setThrowsTypes(emptyList())
+        }
+    }
+
+    override fun isImplicitConstructor(): Boolean = implicitConstructor
+    override fun isConstructor(): Boolean = true
+    override var superConstructor: ConstructorItem? = null
+
+    private var _superMethods: List<MethodItem>? = null
+    override fun superMethods(): List<MethodItem> {
+        if (_superMethods == null) {
+            val result = mutableListOf<MethodItem>()
+            psiMethod.findSuperMethods().mapTo(result) { codebase.findMethod(it) }
+
+            if (result.isEmpty() && isConstructor() && containingClass().superClass() != null) {
+                // Try a little harder; psi findSuperMethod doesn't seem to find super constructors in
+                // some cases, but maybe we can find it by resolving actual super() calls!
+                // TODO: Port to UAST
+                var curr: PsiElement? = psiMethod.body?.firstBodyElement
+                while (curr != null && curr is PsiWhiteSpace) {
+                    curr = curr.nextSibling
+                }
+                if (curr is PsiExpressionStatement && curr.expression is PsiMethodCallExpression &&
+                    curr.expression.firstChild?.lastChild is PsiKeyword &&
+                    curr.expression.firstChild?.lastChild?.text == "super"
+                ) {
+                    val resolved = (curr.expression as PsiMethodCallExpression).resolveMethod()
+                    if (resolved is PsiMethod) {
+                        val superConstructor = codebase.findMethod(resolved)
+                        result.add(superConstructor)
+                    }
+                }
+            }
+            _superMethods = result
+        }
+
+        return _superMethods!!
+    }
+
+    fun findDelegate(predicate: Predicate<Item>, allowInexactMatch: Boolean = true): PsiConstructorItem? {
+        if (isImplicitConstructor()) {
+            // Delegate to parent implicit constructors
+            (containingClass().superClass() as? PsiClassItem)?.constructors()?.forEach {
+                if (it.implicitConstructor) {
+                    if (predicate.test(it)) {
+                        return it
+                    } else {
+                        return it.findDelegate(predicate, allowInexactMatch)
+                    }
+                }
+            }
+        }
+
+        val superPsiMethod = PsiConstructorItem.findSuperOrThis(psiMethod)
+        if (superPsiMethod != null) {
+            val superMethod = codebase.findMethod(superPsiMethod) as PsiConstructorItem
+            if (!predicate.test(superMethod)) {
+                return superMethod.findDelegate(predicate, allowInexactMatch)
+            }
+            return superMethod
+        }
+
+        // Try to pick an alternative - for example adding package private bridging
+        // methods if the super class is in the same package
+        val constructors = (containingClass().superClass() as? PsiClassItem)?.constructors()
+        constructors?.forEach { constructor ->
+            if (predicate.test(constructor)) {
+                return constructor
+            }
+            val superMethod = constructor.findDelegate(predicate, allowInexactMatch)
+            if (superMethod != null) {
+                return superMethod
+            }
+        }
+
+        return null
+    }
+
+    companion object {
+        fun create(
+            codebase: PsiBasedCodebase, containingClass: PsiClassItem,
+            psiMethod: PsiMethod
+        ): PsiConstructorItem {
+            assert(psiMethod.isConstructor)
+            val name = psiMethod.name
+            val commentText = javadoc(psiMethod)
+            val modifiers = modifiers(codebase, psiMethod, commentText)
+            val parameters = psiMethod.parameterList.parameters.mapIndexed { index, parameter ->
+                PsiParameterItem.create(codebase, parameter, index)
+            }
+
+            val constructor = PsiConstructorItem(
+                codebase = codebase,
+                psiMethod = psiMethod,
+                containingClass = containingClass,
+                name = name,
+                documentation = commentText,
+                modifiers = modifiers,
+                parameters = parameters,
+                returnType = codebase.getType(containingClass.psiClass),
+                implicitConstructor = false
+            )
+            constructor.modifiers.setOwner(constructor)
+            return constructor
+        }
+
+        fun createDefaultConstructor(
+            codebase: PsiBasedCodebase,
+            containingClass: PsiClassItem,
+            psiClass: PsiClass
+        ): PsiConstructorItem {
+            val name = psiClass.name!!
+
+            val factory = JavaPsiFacade.getInstance(psiClass.project).elementFactory
+            val psiMethod = factory.createConstructor(name, psiClass)
+            val flags = containingClass.modifiers.getAccessFlags()
+            val modifiers = PsiModifierItem(codebase, flags, null)
+
+            val item = PsiConstructorItem(
+                codebase = codebase,
+                psiMethod = psiMethod,
+                containingClass = containingClass,
+                name = name,
+                documentation = "",
+                modifiers = modifiers,
+                parameters = emptyList(),
+                returnType = codebase.getType(psiClass),
+                implicitConstructor = true
+            )
+            modifiers.setOwner(item)
+            return item
+        }
+
+        fun create(
+            codebase: PsiBasedCodebase,
+            containingClass: PsiClassItem,
+            original: PsiConstructorItem
+        ): PsiConstructorItem {
+            val constructor = PsiConstructorItem(
+                codebase = codebase,
+                psiMethod = original.psiMethod,
+                containingClass = containingClass,
+                name = original.name(),
+                documentation = original.documentation,
+                modifiers = PsiModifierItem.create(codebase, original.modifiers),
+                parameters = PsiParameterItem.create(codebase, original.parameters()),
+                returnType = codebase.getType(containingClass.psiClass),
+                implicitConstructor = original.implicitConstructor
+            )
+
+            constructor.modifiers.setOwner(constructor)
+            constructor.source = original
+
+            return constructor
+        }
+
+        internal fun findSuperOrThis(psiMethod: PsiMethod): PsiMethod? {
+            val superMethods = psiMethod.findSuperMethods()
+            if (superMethods.isNotEmpty()) {
+                return superMethods[0]
+            }
+
+// WARNING: I've deleted private constructors from class model; may not be right for here!
+
+            // TODO: Port to UAST
+            var curr: PsiElement? = psiMethod.body?.firstBodyElement
+            while (curr != null && curr is PsiWhiteSpace) {
+                curr = curr.nextSibling
+            }
+            if (curr is PsiExpressionStatement && curr.expression is PsiMethodCallExpression) {
+                val call = curr.expression as PsiMethodCallExpression
+                if (call.firstChild?.lastChild is PsiKeyword) {
+                    val keyword = call.firstChild?.lastChild
+                    // TODO: Check Kotlin!
+                    if (keyword?.text == "super" || keyword?.text == "this") {
+                        val resolved = call.resolveMethod()
+                        if (resolved is PsiMethod) {
+                            return resolved
+                        }
+                    }
+                }
+            }
+
+            // TODO: Try to find a super call *anywhere* in the method
+
+            // See if we have an implicit constructor in the parent that we can call
+//            psiMethod.containingClass?.constructors?.forEach {
+//                // PsiUtil.hasDefaultConstructor(psiClass) if (it.impl)
+//            }
+
+            return null
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/model/psi/PsiFieldItem.kt b/src/main/java/com/android/tools/metalava/model/psi/PsiFieldItem.kt
new file mode 100644
index 0000000..1a65079
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/model/psi/PsiFieldItem.kt
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava.model.psi
+
+import com.android.tools.metalava.model.ClassItem
+import com.android.tools.metalava.model.FieldItem
+import com.android.tools.metalava.model.TypeItem
+import com.intellij.psi.PsiEnumConstant
+import com.intellij.psi.PsiField
+import com.intellij.psi.impl.JavaConstantExpressionEvaluator
+
+class PsiFieldItem(
+    override val codebase: PsiBasedCodebase,
+    private val psiField: PsiField,
+    private val containingClass: PsiClassItem,
+    private val name: String,
+    modifiers: PsiModifierItem,
+    documentation: String,
+    private val fieldType: PsiTypeItem,
+    private val isEnumConstant: Boolean,
+    private val initialValue: Any?
+) :
+    PsiItem(
+        codebase = codebase,
+        modifiers = modifiers,
+        documentation = documentation,
+        element = psiField
+    ), FieldItem {
+
+    override fun type(): TypeItem = fieldType
+    override fun initialValue(requireConstant: Boolean): Any? {
+        if (initialValue != null) {
+            return initialValue
+        }
+        val constant = psiField.computeConstantValue()
+        if (constant != null) {
+            return constant
+        }
+
+        return if (!requireConstant) {
+            val initializer = psiField.initializer ?: return null
+            JavaConstantExpressionEvaluator.computeConstantExpression(initializer, false)
+        } else {
+            null
+        }
+    }
+
+    override fun isEnumConstant(): Boolean = isEnumConstant
+    override fun name(): String = name
+    override fun containingClass(): ClassItem = containingClass
+
+    override fun duplicate(targetContainingClass: ClassItem): PsiFieldItem {
+        val duplicated = create(codebase, targetContainingClass as PsiClassItem, psiField)
+
+        // Preserve flags that may have been inherited (propagated) fro surrounding packages
+        if (targetContainingClass.hidden) {
+            duplicated.hidden = true
+        }
+        if (targetContainingClass.removed) {
+            duplicated.removed = true
+        }
+        if (targetContainingClass.docOnly) {
+            duplicated.docOnly = true
+        }
+
+        return duplicated
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) {
+            return true
+        }
+        return other is FieldItem && name == other.name() && containingClass == other.containingClass()
+    }
+
+    override fun hashCode(): Int {
+        return name.hashCode()
+    }
+
+    override fun toString(): String = "field ${containingClass.fullName()}.${name()}"
+
+    companion object {
+        fun create(codebase: PsiBasedCodebase, containingClass: PsiClassItem, psiField: PsiField): PsiFieldItem {
+            val name = psiField.name
+            val commentText = javadoc(psiField)
+            val modifiers = modifiers(codebase, psiField, commentText)
+
+            val fieldType = codebase.getType(psiField.type)
+            val isEnumConstant = psiField is PsiEnumConstant
+            val initialValue = null // compute lazily
+
+            val field = PsiFieldItem(
+                codebase = codebase,
+                psiField = psiField,
+                containingClass = containingClass,
+                name = name,
+                documentation = commentText,
+                modifiers = modifiers,
+                fieldType = fieldType,
+                isEnumConstant = isEnumConstant,
+                initialValue = initialValue
+            )
+            field.modifiers.setOwner(field)
+            return field
+        }
+
+        fun create(codebase: PsiBasedCodebase, containingClass: PsiClassItem, original: PsiFieldItem): PsiFieldItem {
+            val field = PsiFieldItem(
+                codebase = codebase,
+                psiField = original.psiField,
+                containingClass = containingClass,
+                name = original.name,
+                documentation = original.documentation,
+                modifiers = PsiModifierItem.create(codebase, original.modifiers),
+                fieldType = PsiTypeItem.create(codebase, original.fieldType),
+                isEnumConstant = original.isEnumConstant,
+                initialValue = original.initialValue
+            )
+            field.modifiers.setOwner(field)
+            return field
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/model/psi/PsiItem.kt b/src/main/java/com/android/tools/metalava/model/psi/PsiItem.kt
new file mode 100644
index 0000000..548bc2f
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/model/psi/PsiItem.kt
@@ -0,0 +1,340 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava.model.psi
+
+import com.android.tools.metalava.model.DefaultItem
+import com.android.tools.metalava.model.Item
+import com.android.tools.metalava.model.MutableModifierList
+import com.android.tools.metalava.model.PackageItem
+import com.android.tools.metalava.model.ParameterItem
+import com.intellij.psi.PsiClass
+import com.intellij.psi.PsiCompiledElement
+import com.intellij.psi.PsiDocCommentOwner
+import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiMember
+import com.intellij.psi.PsiModifierListOwner
+import com.intellij.psi.PsiReference
+import com.intellij.psi.PsiWhiteSpace
+import com.intellij.psi.javadoc.PsiDocTag
+import com.intellij.psi.javadoc.PsiInlineDocTag
+import org.jetbrains.kotlin.kdoc.psi.api.KDoc
+import org.jetbrains.uast.UElement
+import org.jetbrains.uast.sourcePsiElement
+
+abstract class PsiItem(
+    override val codebase: PsiBasedCodebase,
+    val element: PsiElement,
+    override val modifiers: PsiModifierItem,
+    override var documentation: String
+) : DefaultItem() {
+
+    override val deprecated: Boolean get() = modifiers.isDeprecated()
+
+    @Suppress("LeakingThis") // Documentation can change, but we don't want to pick up subsequent @docOnly mutations
+    override var docOnly = documentation.contains("@doconly")
+    @Suppress("LeakingThis")
+    override var removed = documentation.contains("@removed")
+    @Suppress("LeakingThis")
+    override var hidden = (documentation.contains("@hide") || documentation.contains("@pending")
+            || modifiers.hasHideAnnotations()) && !modifiers.hasShowAnnotation()
+
+    override fun psi(): PsiElement? = element
+
+    // TODO: Consider only doing this in tests!
+    override fun isFromClassPath(): Boolean {
+        return if (element is UElement) {
+            element.psi is PsiCompiledElement
+        } else {
+            element is PsiCompiledElement
+        }
+    }
+
+    /** Get a mutable version of modifiers for this item */
+    override fun mutableModifiers(): MutableModifierList = modifiers
+
+    override fun findTagDocumentation(tag: String): String? {
+        if (element is PsiCompiledElement) {
+            return null
+        }
+        if (documentation.isBlank()) {
+            return null
+        }
+
+        // We can't just use element.docComment here because we may have modified
+        // the comment and then the comment snapshot in PSI isn't up to date with our
+        // latest changes
+        val docComment = codebase.getComment(documentation)
+        val docTag = docComment.findTagByName(tag) ?: return null
+        val text = docTag.text
+
+        // Trim trailing next line (javadoc *)
+        var index = text.length - 1
+        while (index > 0) {
+            val c = text[index]
+            if (!(c == '*' || c.isWhitespace())) {
+                break
+            }
+            index--
+        }
+        index++
+        return if (index < text.length) {
+            text.substring(0, index)
+        } else {
+            text
+        }
+    }
+
+    override fun appendDocumentation(comment: String, tagSection: String?, append: Boolean) {
+        if (comment.isBlank()) {
+            return
+        }
+
+        // TODO: Figure out if an annotation should go on the return value, or on the method.
+        // For example; threading: on the method, range: on the return value.
+        // TODO: Find a good way to add or append to a given tag (@param <something>, @return, etc)
+
+        if (this is ParameterItem) {
+            // For parameters, the documentation goes into the surrounding method's documentation!
+            // Find the right parameter location!
+            val parameterName = name()
+            val target = containingMethod()
+            target.appendDocumentation(comment, parameterName)
+            return
+        }
+
+        documentation = mergeDocumentation(documentation, element, comment.trim(), tagSection, append)
+    }
+
+    private fun packageName(): String? {
+        var curr: Item? = this
+        while (curr != null) {
+            if (curr is PackageItem) {
+                return curr.qualifiedName()
+            }
+            curr = curr.parent()
+        }
+
+        return null
+    }
+
+    override fun fullyQualifiedDocumentation(): String {
+        if (documentation.isBlank()) {
+            return documentation
+        }
+
+        if (!(documentation.contains("@link") || // includes @linkplain
+                    documentation.contains("@see") ||
+                    documentation.contains("@throws"))
+        ) {
+            // No relevant tags that need to be expanded/rewritten
+            return documentation
+        }
+
+        val comment =
+            try {
+                codebase.getComment(documentation, psi())
+            } catch (throwable: Throwable) {
+                // TODO: Get rid of line comments as documentation
+                // Invalid comment
+                if (documentation.startsWith("//") && documentation.contains("/**")) {
+                    documentation = documentation.substring(documentation.indexOf("/**"))
+                }
+                codebase.getComment(documentation, psi())
+            }
+        val sb = StringBuilder(documentation.length)
+        var curr = comment.firstChild
+        while (curr != null) {
+            if (curr is PsiDocTag) {
+                sb.append(getExpanded(curr))
+            } else {
+                sb.append(curr.text)
+            }
+            curr = curr.nextSibling
+        }
+
+        return sb.toString()
+    }
+
+    private fun getExpanded(tag: PsiDocTag): String {
+        val text = tag.text
+        var valueElement = tag.valueElement
+        val reference = extractReference(tag)
+        var resolved = reference?.resolve()
+        var referenceText = reference?.element?.text
+        if (resolved == null && tag.name == "throws") {
+            // Workaround: @throws does not provide a valid reference to the class
+            val dataElements = tag.dataElements
+            if (dataElements.isNotEmpty()) {
+                if (dataElements[0] is PsiInlineDocTag) {
+                    val innerReference = extractReference(dataElements[0] as PsiInlineDocTag)
+                    resolved = innerReference?.resolve()
+                    if (innerReference != null && resolved == null) {
+                        referenceText = innerReference.canonicalText
+                        resolved = codebase.createReferenceFromText(referenceText, psi()).resolve()
+                    } else {
+                        referenceText = innerReference?.element?.text
+                    }
+                }
+                if (resolved == null || referenceText == null) {
+                    val exceptionName = dataElements[0].text
+                    val exceptionReference = codebase.createReferenceFromText(exceptionName, psi())
+                    resolved = exceptionReference.resolve()
+                    referenceText = exceptionName
+                } else {
+                    // Create a placeholder value since the inline tag
+                    // wipes it out
+                    val t = dataElements[0].text
+                    val index = text.indexOf(t) + t.length
+                    val suffix = text.substring(index)
+                    val dummyTag = codebase.createDocTagFromText("@${tag.name} $suffix")
+                    valueElement = dummyTag.valueElement
+                }
+            } else {
+                return text
+            }
+        }
+
+        if (resolved != null && referenceText != null) {
+            if (referenceText.startsWith("#")) {
+                // Already a local/relative reference
+                return text
+            }
+
+            when (resolved) {
+            // TODO: If same package, do nothing
+            // TODO: If not absolute, preserve syntax
+                is PsiClass -> {
+                    if (samePackage(resolved)) {
+                        return text
+                    }
+                    val qualifiedName = resolved.qualifiedName ?: return text
+                    if (referenceText == qualifiedName) {
+                        // Already absolute
+                        return text
+                    }
+                    return when {
+                        valueElement != null -> {
+                            val start = valueElement.startOffsetInParent
+                            val end = start + valueElement.textLength
+                            text.substring(0, start) + qualifiedName + text.substring(end)
+                        }
+                        tag.name == "see" -> {
+                            val suffix = text.substring(text.indexOf(referenceText) + referenceText.length)
+                            "@see $qualifiedName$suffix"
+                        }
+                        text.startsWith("{") -> "{@${tag.name} $qualifiedName $referenceText}"
+                        else -> "@${tag.name} $qualifiedName $referenceText"
+                    }
+                }
+                is PsiMember -> {
+                    val containing = resolved.containingClass ?: return text
+                    if (samePackage(containing)) {
+                        return text
+                    }
+                    val qualifiedName = containing.qualifiedName ?: return text
+                    if (referenceText.startsWith(qualifiedName)) {
+                        // Already absolute
+                        return text
+                    }
+
+                    val name = containing.name ?: return text
+                    if (valueElement != null) {
+                        val start = valueElement.startOffsetInParent
+                        val close = text.lastIndexOf('}')
+                        if (close == -1) {
+                            return text // invalid javadoc
+                        }
+                        val memberPart = text.substring(text.indexOf(name, start) + name.length, close)
+                        return "${text.substring(0, start)}$qualifiedName$memberPart $referenceText}"
+                    }
+                }
+            }
+        }
+
+        return text
+    }
+
+    private fun samePackage(cls: PsiClass): Boolean {
+        val pkg = packageName() ?: return false
+        return cls.qualifiedName == "$pkg.${cls.name}"
+    }
+
+    // Copied from UnnecessaryJavaDocLinkInspection
+    private fun extractReference(tag: PsiDocTag): PsiReference? {
+        val valueElement = tag.valueElement
+        if (valueElement != null) {
+            return valueElement.reference
+        }
+        // hack around the fact that a reference to a class is apparently
+        // not a PsiDocTagValue
+        val dataElements = tag.dataElements
+        if (dataElements.isEmpty()) {
+            return null
+        }
+        val salientElement: PsiElement = dataElements.firstOrNull { it !is PsiWhiteSpace } ?: return null
+        val child = salientElement.firstChild
+        return if (child !is PsiReference) null else child
+    }
+
+    /** Finish initialization of the item */
+    open fun finishInitialization() {
+        modifiers.setOwner(this)
+    }
+
+    companion object {
+        fun javadoc(element: PsiElement): String {
+            if (element is PsiCompiledElement) {
+                return ""
+            }
+
+            if (element is UElement) {
+                val comments = element.comments
+                if (comments.isNotEmpty()) {
+                    val sb = StringBuilder()
+                    comments.asSequence().joinTo(buffer = sb, separator = "\n")
+                    return sb.toString()
+                } else {
+                    // Temporary workaround: UAST seems to not return document nodes
+                    // https://youtrack.jetbrains.com/issue/KT-22135
+                    val first = element.sourcePsiElement?.firstChild
+                    if (first is KDoc) {
+                        return first.text
+                    }
+
+                }
+            }
+
+            if (element is PsiDocCommentOwner) {
+                return element.docComment?.text ?: ""
+            }
+
+            return ""
+        }
+
+        fun modifiers(
+            codebase: PsiBasedCodebase,
+            element: PsiModifierListOwner,
+            documentation: String
+        ): PsiModifierItem {
+            return PsiModifierItem.create(codebase, element, documentation)
+        }
+
+        fun isKotlin(element: PsiElement): Boolean {
+            return element.language.id == "kotlin"
+        }
+    }
+}
diff --git a/src/main/java/com/android/tools/metalava/model/psi/PsiMethodItem.kt b/src/main/java/com/android/tools/metalava/model/psi/PsiMethodItem.kt
new file mode 100644
index 0000000..3c531b8
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/model/psi/PsiMethodItem.kt
@@ -0,0 +1,294 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava.model.psi
+
+import com.android.tools.metalava.compatibility
+import com.android.tools.metalava.model.ClassItem
+import com.android.tools.metalava.model.MethodItem
+import com.android.tools.metalava.model.ModifierList
+import com.android.tools.metalava.model.ParameterItem
+import com.android.tools.metalava.model.TypeItem
+import com.intellij.psi.PsiMethod
+import com.intellij.psi.util.PsiTypesUtil
+import com.intellij.psi.util.TypeConversionUtil
+import org.intellij.lang.annotations.Language
+import java.io.StringWriter
+
+open class PsiMethodItem(
+    override val codebase: PsiBasedCodebase,
+    val psiMethod: PsiMethod,
+    private val containingClass: PsiClassItem,
+    private val name: String,
+    modifiers: PsiModifierItem,
+    documentation: String,
+    private val returnType: PsiTypeItem,
+    private val parameters: List<PsiParameterItem>
+) :
+    PsiItem(
+        codebase = codebase,
+        modifiers = modifiers,
+        documentation = documentation,
+        element = psiMethod
+    ), MethodItem {
+
+    init {
+        for (parameter in parameters) {
+            @Suppress("LeakingThis")
+            parameter.containingMethod = this
+        }
+    }
+
+    /**
+     * If this item was created by filtering down a different codebase, this temporarily
+     * points to the original item during construction. This is used to let us initialize
+     * for example throws lists later, when all classes in the codebase have been
+     * initialized.
+     */
+    internal var source: PsiMethodItem? = null
+
+    override var inheritedInterfaceMethod: Boolean = false
+
+    override fun name(): String = name
+    override fun containingClass(): PsiClassItem = containingClass
+
+    override fun equals(other: Any?): Boolean {
+        // TODO: Allow mix and matching with other MethodItems?
+        if (this === other) return true
+        if (javaClass != other?.javaClass) return false
+
+        other as PsiMethodItem
+
+        if (psiMethod != other.psiMethod) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        return psiMethod.hashCode()
+    }
+
+    override fun isConstructor(): Boolean = false
+
+    override fun isImplicitConstructor(): Boolean = false
+
+    override fun returnType(): TypeItem? = returnType
+
+    override fun parameters(): List<ParameterItem> = parameters
+
+    private var superMethods: List<MethodItem>? = null
+    override fun superMethods(): List<MethodItem> {
+        if (superMethods == null) {
+            val result = mutableListOf<MethodItem>()
+            psiMethod.findSuperMethods().mapTo(result) { codebase.findMethod(it) }
+            superMethods = result
+        }
+
+        return superMethods!!
+    }
+
+    fun setSuperMethods(superMethods: List<MethodItem>) {
+        this.superMethods = superMethods
+    }
+
+    override fun typeParameterList(): String? {
+        return PsiTypeItem.typeParameterList(psiMethod.typeParameterList)
+    }
+
+    override fun typeArgumentClasses(): List<ClassItem> {
+        return PsiTypeItem.typeParameterClasses(codebase, psiMethod.typeParameterList)
+    }
+
+    //    private var throwsTypes: List<ClassItem>? = null
+    private lateinit var throwsTypes: List<ClassItem>
+
+    fun setThrowsTypes(throwsTypes: List<ClassItem>) {
+        this.throwsTypes = throwsTypes
+    }
+
+    override fun throwsTypes(): List<ClassItem> = throwsTypes
+
+    override fun duplicate(targetContainingClass: ClassItem): PsiMethodItem {
+        val duplicated = create(codebase, targetContainingClass as PsiClassItem, psiMethod)
+
+        // Preserve flags that may have been inherited (propagated) fro surrounding packages
+        if (targetContainingClass.hidden) {
+            duplicated.hidden = true
+        }
+        if (targetContainingClass.removed) {
+            duplicated.removed = true
+        }
+        if (targetContainingClass.docOnly) {
+            duplicated.docOnly = true
+        }
+
+        duplicated.throwsTypes = throwsTypes
+        return duplicated
+    }
+
+    /* Call corresponding PSI utility method -- if I can find it!
+    override fun matches(other: MethodItem): Boolean {
+        if (other !is PsiMethodItem) {
+            return super.matches(other)
+        }
+
+        // TODO: Find better API: this also checks surrounding class which we don't want!
+        return psiMethod.isEquivalentTo(other.psiMethod)
+    }
+    */
+
+    @Language("JAVA")
+    fun toStub(replacementMap: Map<String, String> = emptyMap()): String {
+        val method = this
+        // There are type variables; we have to recreate the method signature
+        val sb = StringBuilder(100)
+
+        val modifierString = StringWriter()
+        ModifierList.write(
+            modifierString, method.modifiers, method, removeAbstract = false,
+            removeFinal = false, addPublic = true
+        )
+        sb.append(modifierString.toString())
+
+        val typeParameters = typeParameterList()
+        if (typeParameters != null) {
+            sb.append(' ')
+            sb.append(TypeItem.convertTypeString(typeParameters, replacementMap))
+        }
+
+        val returnType = method.returnType()
+        sb.append(returnType?.convertTypeString(replacementMap))
+
+        sb.append(' ')
+        sb.append(method.name())
+
+        sb.append("(")
+        method.parameters().asSequence().forEachIndexed { i, parameter ->
+            if (i > 0) {
+                sb.append(", ")
+            }
+
+            sb.append(parameter.type().convertTypeString(replacementMap))
+            sb.append(' ')
+            sb.append(parameter.name())
+        }
+        sb.append(")")
+
+        val throws = method.throwsTypes().asSequence().sortedWith(ClassItem.fullNameComparator)
+        if (throws.any()) {
+            sb.append(" throws ")
+            throws.asSequence().sortedWith(ClassItem.fullNameComparator).forEachIndexed { i, type ->
+                if (i > 0) {
+                    sb.append(", ")
+                }
+                // No need to replace variables; we can't have type arguments for exceptions
+                sb.append(type.qualifiedName())
+            }
+        }
+
+        sb.append(" { return ")
+        val defaultValue = PsiTypesUtil.getDefaultValueOfType(method.psiMethod.returnType)
+        sb.append(defaultValue)
+        sb.append("; }")
+
+        return sb.toString()
+    }
+
+    override fun finishInitialization() {
+        super.finishInitialization()
+
+        throwsTypes = throwsTypes(codebase, psiMethod)
+    }
+
+    companion object {
+        fun create(
+            codebase: PsiBasedCodebase,
+            containingClass: PsiClassItem,
+            psiMethod: PsiMethod
+        ): PsiMethodItem {
+            assert(!psiMethod.isConstructor)
+            val name = psiMethod.name
+            val commentText = javadoc(psiMethod)
+            val modifiers = modifiers(codebase, psiMethod, commentText)
+            val parameters = psiMethod.parameterList.parameters.mapIndexed { index, parameter ->
+                PsiParameterItem.create(codebase, parameter, index)
+            }
+            val returnType = codebase.getType(psiMethod.returnType!!)
+            val method = PsiMethodItem(
+                codebase = codebase,
+                psiMethod = psiMethod,
+                containingClass = containingClass,
+                name = name,
+                documentation = commentText,
+                modifiers = modifiers,
+                returnType = returnType,
+                parameters = parameters
+            )
+            method.modifiers.setOwner(method)
+            return method
+        }
+
+        fun create(
+            codebase: PsiBasedCodebase,
+            containingClass: PsiClassItem,
+            original: PsiMethodItem
+        ): PsiMethodItem {
+            val method = PsiMethodItem(
+                codebase = codebase,
+                psiMethod = original.psiMethod,
+                containingClass = containingClass,
+                name = original.name(),
+                documentation = original.documentation,
+                modifiers = PsiModifierItem.create(codebase, original.modifiers),
+                returnType = PsiTypeItem.create(codebase, original.returnType),
+                parameters = PsiParameterItem.create(codebase, original.parameters())
+            )
+            method.modifiers.setOwner(method)
+            method.source = original
+            method.inheritedInterfaceMethod = original.inheritedInterfaceMethod
+
+            return method
+        }
+
+        private fun throwsTypes(codebase: PsiBasedCodebase, psiMethod: PsiMethod): List<ClassItem> {
+            val interfaces = psiMethod.throwsList.referencedTypes
+            if (interfaces.isEmpty()) {
+                return emptyList()
+            }
+
+            val result = ArrayList<ClassItem>(interfaces.size)
+            for (cls in interfaces) {
+                if (compatibility.useErasureInThrows) {
+                    val erased = TypeConversionUtil.erasure(cls)
+                    result.add(codebase.findClass(erased) ?: continue)
+                    continue
+                }
+
+                result.add(codebase.findClass(cls) ?: continue)
+            }
+
+            // We're sorting the names here even though outputs typically do their own sorting,
+            // since for example the MethodItem.sameSignature check wants to do an element-by-element
+            // comparison to see if the signature matches, and that should match overrides even if
+            // they specify their elements in different orders.
+            result.sortWith(ClassItem.fullNameComparator)
+            return result
+        }
+    }
+
+    override fun toString(): String = "${if (isConstructor()) "constructor" else "method"} ${
+    containingClass.qualifiedName()}.${name()}(${parameters().joinToString { it.type().toSimpleType() }})"
+}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/model/psi/PsiModifierItem.kt b/src/main/java/com/android/tools/metalava/model/psi/PsiModifierItem.kt
new file mode 100644
index 0000000..66a27f9
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/model/psi/PsiModifierItem.kt
@@ -0,0 +1,298 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava.model.psi
+
+import com.android.tools.metalava.model.AnnotationItem
+import com.android.tools.metalava.model.Codebase
+import com.android.tools.metalava.model.Item
+import com.android.tools.metalava.model.ModifierList
+import com.android.tools.metalava.model.MutableModifierList
+import com.intellij.psi.PsiDocCommentOwner
+import com.intellij.psi.PsiModifier
+import com.intellij.psi.PsiModifierList
+import com.intellij.psi.PsiModifierListOwner
+
+class PsiModifierItem(
+    override val codebase: Codebase,
+    var flags: Int = 0,
+    private var annotations: MutableList<AnnotationItem>? = null
+) : ModifierList, MutableModifierList {
+    private lateinit var owner: Item
+
+    private operator fun set(mask: Int, set: Boolean) {
+        flags = if (set) {
+            flags or mask
+        } else {
+            flags and mask.inv()
+        }
+    }
+
+    private fun isSet(mask: Int): Boolean {
+        return flags and mask != 0
+    }
+
+    override fun annotations(): List<AnnotationItem> {
+        return annotations ?: emptyList()
+    }
+
+    override fun owner(): Item {
+        return owner
+    }
+
+    fun setOwner(owner: Item) {
+        this.owner = owner
+    }
+
+    override fun isPublic(): Boolean {
+        return isSet(PUBLIC)
+    }
+
+    override fun isProtected(): Boolean {
+        return isSet(PROTECTED)
+    }
+
+    override fun isPrivate(): Boolean {
+        return isSet(PRIVATE)
+    }
+
+    override fun isStatic(): Boolean {
+        return isSet(STATIC)
+    }
+
+    override fun isAbstract(): Boolean {
+        return isSet(ABSTRACT)
+    }
+
+    override fun isFinal(): Boolean {
+        return isSet(FINAL)
+    }
+
+    override fun isNative(): Boolean {
+        return isSet(NATIVE)
+    }
+
+    override fun isSynchronized(): Boolean {
+        return isSet(SYNCHRONIZED)
+    }
+
+    override fun isStrictFp(): Boolean {
+        return isSet(STRICT_FP)
+    }
+
+    override fun isTransient(): Boolean {
+        return isSet(TRANSIENT)
+    }
+
+    override fun isVolatile(): Boolean {
+        return isSet(VOLATILE)
+    }
+
+    override fun isDefault(): Boolean {
+        return isSet(DEFAULT)
+    }
+
+    fun isDeprecated(): Boolean {
+        return isSet(DEPRECATED)
+    }
+
+    override fun setPublic(public: Boolean) {
+        set(PUBLIC, public)
+    }
+
+    override fun setProtected(protected: Boolean) {
+        set(PROTECTED, protected)
+    }
+
+    override fun setPrivate(private: Boolean) {
+        set(PRIVATE, private)
+    }
+
+    override fun setStatic(static: Boolean) {
+        set(STATIC, static)
+    }
+
+    override fun setAbstract(abstract: Boolean) {
+        set(ABSTRACT, abstract)
+    }
+
+    override fun setFinal(final: Boolean) {
+        set(FINAL, final)
+    }
+
+    override fun setNative(native: Boolean) {
+        set(NATIVE, native)
+    }
+
+    override fun setSynchronized(synchronized: Boolean) {
+        set(SYNCHRONIZED, synchronized)
+    }
+
+    override fun setStrictFp(strictfp: Boolean) {
+        set(STRICT_FP, strictfp)
+    }
+
+    override fun setTransient(transient: Boolean) {
+        set(TRANSIENT, transient)
+    }
+
+    override fun setVolatile(volatile: Boolean) {
+        set(VOLATILE, volatile)
+    }
+
+    override fun setDefault(default: Boolean) {
+        set(DEFAULT, default)
+    }
+
+    fun setDeprecated(deprecated: Boolean) {
+        set(DEPRECATED, deprecated)
+    }
+
+    override fun addAnnotation(annotation: AnnotationItem) {
+        if (annotations == null) {
+            annotations = mutableListOf()
+        }
+        annotations?.add(annotation)
+    }
+
+    override fun removeAnnotation(annotation: AnnotationItem) {
+        annotations?.remove(annotation)
+    }
+
+    override fun clearAnnotations(annotation: AnnotationItem) {
+        annotations?.clear()
+    }
+
+    override fun isEmpty(): Boolean {
+        return flags and DEPRECATED.inv() == 0 // deprecated isn't a real modifier
+    }
+
+    override fun isPackagePrivate(): Boolean {
+        return flags and (PUBLIC or PROTECTED or PRIVATE) == 0
+    }
+
+    fun getAccessFlags(): Int {
+        return flags and (PUBLIC or PROTECTED or PRIVATE)
+    }
+
+    // Rename? It's not a full equality, it's whether an override's modifier set is significant
+    override fun equivalentTo(other: ModifierList): Boolean {
+        if (other is PsiModifierItem) {
+            val flags2 = other.flags
+            val mask = EQUIVALENCE_MASK
+
+            // Skipping the "default" flag
+            // TODO: Compatibility: skipnative modiifier and skipstrictfp modifier flags!
+            //if (!compatibility.skipNativeModifier && isNative() != other.isNative()) return false
+            //if (!compatibility.skipStrictFpModifier && isStrictFp() != other.isStrictFp()) return false
+            return flags and mask == flags2 and mask
+        }
+        return false
+    }
+
+    companion object {
+        const val PUBLIC = 1 shl 0
+        const val PROTECTED = 1 shl 1
+        const val PRIVATE = 1 shl 2
+        const val STATIC = 1 shl 3
+        const val ABSTRACT = 1 shl 4
+        const val FINAL = 1 shl 5
+        const val NATIVE = 1 shl 6
+        const val SYNCHRONIZED = 1 shl 7
+        const val STRICT_FP = 1 shl 8
+        const val TRANSIENT = 1 shl 9
+        const val VOLATILE = 1 shl 10
+        const val DEFAULT = 1 shl 11
+        const val DEPRECATED = 1 shl 12
+
+        private const val EQUIVALENCE_MASK = PUBLIC or PROTECTED or PRIVATE or STATIC or ABSTRACT or
+                FINAL or TRANSIENT or VOLATILE or SYNCHRONIZED or DEPRECATED
+
+        fun create(codebase: PsiBasedCodebase, element: PsiModifierListOwner, documentation: String?): PsiModifierItem {
+            val modifiers = create(
+                codebase,
+                element.modifierList
+            )
+
+            if (documentation?.contains("@deprecated") == true ||
+                // Check for @Deprecated annotation
+                ((element as? PsiDocCommentOwner)?.isDeprecated == true)
+            ) {
+                modifiers.setDeprecated(true)
+            }
+
+            return modifiers
+        }
+
+        fun create(codebase: PsiBasedCodebase, modifierList: PsiModifierList?): PsiModifierItem {
+            modifierList ?: return PsiModifierItem(codebase)
+
+            var flags = 0
+            if (modifierList.hasModifierProperty(PsiModifier.PUBLIC)) {
+                flags = flags or PUBLIC
+            }
+            if (modifierList.hasModifierProperty(PsiModifier.PROTECTED)) {
+                flags = flags or PROTECTED
+            }
+            if (modifierList.hasModifierProperty(PsiModifier.PRIVATE)) {
+                flags = flags or PRIVATE
+            }
+            if (modifierList.hasModifierProperty(PsiModifier.STATIC)) {
+                flags = flags or STATIC
+            }
+            if (modifierList.hasModifierProperty(PsiModifier.ABSTRACT)) {
+                flags = flags or ABSTRACT
+            }
+            if (modifierList.hasModifierProperty(PsiModifier.FINAL)) {
+                flags = flags or FINAL
+            }
+            if (modifierList.hasModifierProperty(PsiModifier.NATIVE)) {
+                flags = flags or NATIVE
+            }
+            if (modifierList.hasModifierProperty(PsiModifier.SYNCHRONIZED)) {
+                flags = flags or SYNCHRONIZED
+            }
+            if (modifierList.hasModifierProperty(PsiModifier.STRICTFP)) {
+                flags = flags or STRICT_FP
+            }
+            if (modifierList.hasModifierProperty(PsiModifier.TRANSIENT)) {
+                flags = flags or TRANSIENT
+            }
+            if (modifierList.hasModifierProperty(PsiModifier.VOLATILE)) {
+                flags = flags or VOLATILE
+            }
+            if (modifierList.hasModifierProperty(PsiModifier.DEFAULT)) {
+                flags = flags or DEFAULT
+            }
+
+            val psiAnnotations = modifierList.annotations
+            return if (psiAnnotations.isEmpty()) {
+                PsiModifierItem(codebase, flags)
+            } else {
+                val annotations: MutableList<AnnotationItem> =
+                    psiAnnotations.map { PsiAnnotationItem.create(codebase, it) }.toMutableList()
+                PsiModifierItem(codebase, flags, annotations)
+            }
+        }
+
+        fun create(codebase: PsiBasedCodebase, original: PsiModifierItem): PsiModifierItem {
+            val originalAnnotations = original.annotations ?: return PsiModifierItem(codebase, original.flags)
+            val copy: MutableList<AnnotationItem> = ArrayList(originalAnnotations.size)
+            originalAnnotations.mapTo(copy) { PsiAnnotationItem.create(codebase, it as PsiAnnotationItem) }
+            return PsiModifierItem(codebase, original.flags, copy)
+        }
+    }
+}
diff --git a/src/main/java/com/android/tools/metalava/model/psi/PsiPackageItem.kt b/src/main/java/com/android/tools/metalava/model/psi/PsiPackageItem.kt
new file mode 100644
index 0000000..a4fa5e4
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/model/psi/PsiPackageItem.kt
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava.model.psi
+
+import com.android.tools.metalava.model.ClassItem
+import com.android.tools.metalava.model.PackageItem
+import com.android.tools.metalava.options
+import com.intellij.psi.PsiPackage
+
+class PsiPackageItem(
+    override val codebase: PsiBasedCodebase,
+    private val psiPackage: PsiPackage,
+    private val qualifiedName: String,
+    modifiers: PsiModifierItem,
+    documentation: String
+) :
+    PsiItem(
+        codebase = codebase,
+        modifiers = modifiers,
+        documentation = documentation,
+        element = psiPackage
+    ), PackageItem {
+    // Note - top level classes only
+    private val classes: MutableList<PsiClassItem> = mutableListOf()
+
+    override fun topLevelClasses(): Sequence<ClassItem> = classes.asSequence().filter { it.isTopLevelClass() }
+
+    lateinit var containingPackageField: PsiPackageItem
+
+    override var hidden: Boolean = super.hidden || options.hidePackages.contains(qualifiedName)
+
+    override fun containingPackage(): PackageItem? {
+        return if (qualifiedName.isEmpty()) null else {
+            if (!::containingPackageField.isInitialized) {
+                var parentPackage = qualifiedName
+                while (true) {
+                    val index = parentPackage.lastIndexOf('.')
+                    if (index == -1) {
+                        containingPackageField = codebase.findPackage("")!!
+                        return containingPackageField
+                    }
+                    parentPackage = parentPackage.substring(0, index)
+                    val pkg = codebase.findPackage(parentPackage)
+                    if (pkg != null) {
+                        containingPackageField = pkg
+                        return pkg
+                    }
+                }
+
+                @Suppress("UNREACHABLE_CODE")
+                null
+            } else {
+                containingPackageField
+            }
+        }
+    }
+
+    fun addClass(cls: PsiClassItem) {
+        if (!cls.isTopLevelClass()) {
+            // TODO: Stash in a list somewhere to make allClasses() faster?
+            return
+        }
+
+        /*
+        // Temp debugging:
+        val q = cls.qualifiedName()
+        for (c in classes) {
+            if (q == c.qualifiedName()) {
+                assert(false, { "Unexpectedly found class $q already listed in $this" })
+                return
+            }
+        }
+        */
+
+        classes.add(cls)
+        cls.containingPackage = this
+    }
+
+    fun addClasses(classList: List<PsiClassItem>) {
+        for (cls in classList) {
+            addClass(cls)
+        }
+    }
+
+    override fun qualifiedName(): String = qualifiedName
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) {
+            return true
+        }
+        return other is PackageItem && qualifiedName == other.qualifiedName()
+    }
+
+    override fun hashCode(): Int = qualifiedName.hashCode()
+
+    override fun toString(): String = "Package $qualifiedName"
+
+    override fun finishInitialization() {
+        super.finishInitialization()
+        val initialClasses = ArrayList(classes)
+        var original = initialClasses.size // classes added after this point will have indices >= original
+        for (cls in initialClasses) {
+            cls.finishInitialization()
+        }
+
+        // Finish initialization of any additional classes that were registered during
+        // the above initialization (recursively)
+        while (original < classes.size) {
+            val added = ArrayList(classes.subList(original, classes.size))
+            original = classes.size
+            for (cls in added) {
+                cls.finishInitialization()
+            }
+        }
+    }
+
+    companion object {
+        fun create(codebase: PsiBasedCodebase, psiPackage: PsiPackage, extraDocs: String?): PsiPackageItem {
+            val commentText = javadoc(psiPackage) + if (extraDocs != null) "\n$extraDocs" else ""
+            val modifiers = modifiers(codebase, psiPackage, commentText)
+            if (modifiers.isPackagePrivate()) {
+                modifiers.setPublic(true) // packages are always public (if not hidden explicitly with private)
+            }
+            val qualifiedName = psiPackage.qualifiedName
+
+            val pkg = PsiPackageItem(
+                codebase = codebase,
+                psiPackage = psiPackage,
+                qualifiedName = qualifiedName,
+                documentation = commentText,
+                modifiers = modifiers
+            )
+            pkg.modifiers.setOwner(pkg)
+            return pkg
+        }
+
+        fun create(codebase: PsiBasedCodebase, original: PsiPackageItem): PsiPackageItem {
+            val pkg = PsiPackageItem(
+                codebase = codebase,
+                psiPackage = original.psiPackage,
+                qualifiedName = original.qualifiedName,
+                documentation = original.documentation,
+                modifiers = PsiModifierItem.create(codebase, original.modifiers)
+            )
+            pkg.modifiers.setOwner(pkg)
+            return pkg
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/model/psi/PsiParameterItem.kt b/src/main/java/com/android/tools/metalava/model/psi/PsiParameterItem.kt
new file mode 100644
index 0000000..d44184a
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/model/psi/PsiParameterItem.kt
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava.model.psi
+
+import com.android.tools.metalava.model.MethodItem
+import com.android.tools.metalava.model.ParameterItem
+import com.android.tools.metalava.model.TypeItem
+import com.intellij.psi.PsiParameter
+import org.jetbrains.kotlin.psi.psiUtil.parameterIndex
+
+class PsiParameterItem(
+    override val codebase: PsiBasedCodebase,
+    private val psiParameter: PsiParameter,
+    private val name: String,
+    override val parameterIndex: Int,
+    modifiers: PsiModifierItem,
+    documentation: String,
+    private val type: PsiTypeItem
+) : PsiItem(
+    codebase = codebase,
+    modifiers = modifiers,
+    documentation = documentation,
+    element = psiParameter
+), ParameterItem {
+    lateinit var containingMethod: PsiMethodItem
+
+    override fun name(): String = name
+
+    override fun publicName(): String? {
+        if (isKotlin(psiParameter)) {
+            if (name == "\$receiver") {
+                return null
+            }
+            return name
+        } else {
+            // Java: Look for @ParameterName annotation
+            val annotation = modifiers.annotations().firstOrNull { it.isParameterName() }
+            if (annotation != null) {
+                return annotation.attributes().firstOrNull()?.value?.value()?.toString()
+            }
+        }
+
+        return null
+    }
+
+    override fun type(): TypeItem = type
+    override fun containingMethod(): MethodItem = containingMethod
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) {
+            return true
+        }
+        return other is ParameterItem && parameterIndex == other.parameterIndex && containingMethod == other.containingMethod()
+    }
+
+    override fun hashCode(): Int {
+        return parameterIndex
+    }
+
+    override fun toString(): String = "parameter ${name()}"
+
+    companion object {
+        fun create(
+            codebase: PsiBasedCodebase,
+            psiParameter: PsiParameter,
+            parameterIndex: Int
+        ): PsiParameterItem {
+            val name = psiParameter.name ?: "arg${psiParameter.parameterIndex() + 1}"
+            val commentText = "" // no javadocs on individual parameters
+            val modifiers = modifiers(codebase, psiParameter, commentText)
+            val type = codebase.getType(psiParameter.type)
+            val parameter = PsiParameterItem(
+                codebase = codebase,
+                psiParameter = psiParameter,
+                name = name,
+                parameterIndex = parameterIndex,
+                documentation = commentText,
+                modifiers = modifiers,
+                type = type
+            )
+            parameter.modifiers.setOwner(parameter)
+            return parameter
+        }
+
+        fun create(
+            codebase: PsiBasedCodebase,
+            original: PsiParameterItem
+        ): PsiParameterItem {
+            val parameter = PsiParameterItem(
+                codebase = codebase,
+                psiParameter = original.psiParameter,
+                name = original.name,
+                parameterIndex = original.parameterIndex,
+                documentation = original.documentation,
+                modifiers = PsiModifierItem.create(codebase, original.modifiers),
+                type = PsiTypeItem.create(codebase, original.type)
+            )
+            parameter.modifiers.setOwner(parameter)
+            return parameter
+        }
+
+        fun create(
+            codebase: PsiBasedCodebase,
+            original: List<ParameterItem>
+        ): List<PsiParameterItem> {
+            return original.map { create(codebase, it as PsiParameterItem) }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/model/psi/PsiTypeItem.kt b/src/main/java/com/android/tools/metalava/model/psi/PsiTypeItem.kt
new file mode 100644
index 0000000..e9ad7ba
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/model/psi/PsiTypeItem.kt
@@ -0,0 +1,756 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava.model.psi
+
+import com.android.tools.lint.detector.api.LintUtils
+import com.android.tools.metalava.compatibility
+import com.android.tools.metalava.doclava1.ApiPredicate
+import com.android.tools.metalava.model.AnnotationItem
+import com.android.tools.metalava.model.ClassItem
+import com.android.tools.metalava.model.Codebase
+import com.android.tools.metalava.model.Item
+import com.android.tools.metalava.model.TypeItem
+import com.android.tools.metalava.model.text.TextTypeItem
+import com.intellij.psi.JavaTokenType
+import com.intellij.psi.PsiArrayType
+import com.intellij.psi.PsiCapturedWildcardType
+import com.intellij.psi.PsiClass
+import com.intellij.psi.PsiClassType
+import com.intellij.psi.PsiCompiledElement
+import com.intellij.psi.PsiDisjunctionType
+import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiEllipsisType
+import com.intellij.psi.PsiIntersectionType
+import com.intellij.psi.PsiJavaCodeReferenceElement
+import com.intellij.psi.PsiJavaToken
+import com.intellij.psi.PsiLambdaExpressionType
+import com.intellij.psi.PsiPrimitiveType
+import com.intellij.psi.PsiRecursiveElementVisitor
+import com.intellij.psi.PsiReferenceList
+import com.intellij.psi.PsiType
+import com.intellij.psi.PsiTypeElement
+import com.intellij.psi.PsiTypeParameter
+import com.intellij.psi.PsiTypeParameterList
+import com.intellij.psi.PsiTypeVisitor
+import com.intellij.psi.PsiWhiteSpace
+import com.intellij.psi.PsiWildcardType
+import com.intellij.psi.util.PsiTypesUtil
+import com.intellij.psi.util.TypeConversionUtil
+
+/** Represents a type backed by PSI */
+class PsiTypeItem private constructor(private val codebase: PsiBasedCodebase, private val psiType: PsiType) : TypeItem {
+    private var toString: String? = null
+    private var toAnnotatedString: String? = null
+    private var toInnerAnnotatedString: String? = null
+    private var toErasedString: String? = null
+    private var asClass: ClassItem? = null
+
+    override fun toString(): String {
+        return toTypeString()
+    }
+
+    override fun toTypeString(
+        outerAnnotations: Boolean,
+        innerAnnotations: Boolean,
+        erased: Boolean
+    ): String {
+        assert(innerAnnotations || !outerAnnotations) // Can't supply outer=true,inner=false
+
+        return if (erased) {
+            if (innerAnnotations || outerAnnotations) {
+                // Not cached: Not common
+                toTypeString(codebase, psiType, outerAnnotations, innerAnnotations, erased)
+            } else {
+                if (toErasedString == null) {
+                    toErasedString = toTypeString(codebase, psiType, outerAnnotations, innerAnnotations, erased)
+                }
+                toErasedString!!
+            }
+        } else {
+            when {
+                outerAnnotations -> {
+                    if (toAnnotatedString == null) {
+                        toAnnotatedString = TypeItem.formatType(
+                            toTypeString(
+                                codebase,
+                                psiType,
+                                outerAnnotations,
+                                innerAnnotations,
+                                erased
+                            )
+                        )
+                    }
+                    toAnnotatedString!!
+                }
+                innerAnnotations -> {
+                    if (toInnerAnnotatedString == null) {
+                        toInnerAnnotatedString = TypeItem.formatType(
+                            toTypeString(
+                                codebase,
+                                psiType,
+                                outerAnnotations,
+                                innerAnnotations,
+                                erased
+                            )
+                        )
+                    }
+                    toInnerAnnotatedString!!
+                }
+                else -> {
+                    if (toString == null) {
+                        toString = TypeItem.formatType(getCanonicalText(psiType, annotated = false))
+                    }
+                    toString!!
+                }
+            }
+        }
+    }
+
+    override fun toErasedTypeString(): String {
+        return toTypeString(outerAnnotations = false, innerAnnotations = false, erased = true)
+    }
+
+    override fun isTypeParameter(): Boolean {
+        return asClass()?.psi() is PsiTypeParameter
+    }
+
+    override fun internalName(): String {
+        if (primitive) {
+            val signature = getPrimitiveSignature(toString())
+            if (signature != null) {
+                return signature
+            }
+        }
+        val sb = StringBuilder()
+        appendJvmSignature(sb, psiType)
+        return sb.toString()
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+
+        return when (other) {
+            is TypeItem -> toTypeString().replace(" ", "") == other.toTypeString().replace(" ", "")
+            else -> false
+        }
+    }
+
+    override fun asClass(): ClassItem? {
+        if (asClass == null) {
+            asClass = codebase.findClass(psiType)
+        }
+        return asClass
+    }
+
+    override fun hashCode(): Int {
+        return psiType.hashCode()
+    }
+
+    override val primitive: Boolean
+        get() = psiType is PsiPrimitiveType
+
+    override fun defaultValue(): Any? {
+        return PsiTypesUtil.getDefaultValue(psiType)
+    }
+
+    override fun defaultValueString(): String {
+        return PsiTypesUtil.getDefaultValueOfType(psiType)
+    }
+
+    override fun typeArgumentClasses(): List<ClassItem> {
+        if (primitive) {
+            return emptyList()
+        }
+
+        val classes = mutableListOf<ClassItem>()
+        psiType.accept(object : PsiTypeVisitor<PsiType>() {
+            override fun visitType(type: PsiType?): PsiType? {
+                return type
+            }
+
+            override fun visitClassType(classType: PsiClassType): PsiType? {
+                codebase.findClass(classType)?.let {
+                    if (!it.isTypeParameter && !classes.contains(it)) {
+                        classes.add(it)
+                    }
+                }
+                for (type in classType.parameters) {
+                    type.accept(this)
+                }
+                return classType
+            }
+
+            override fun visitWildcardType(wildcardType: PsiWildcardType): PsiType? {
+                if (wildcardType.isExtends) {
+                    wildcardType.extendsBound.accept(this)
+                }
+                if (wildcardType.isSuper) {
+                    wildcardType.superBound.accept(this)
+                }
+                if (wildcardType.isBounded) {
+                    wildcardType.bound?.accept(this)
+                }
+                return wildcardType
+            }
+
+            override fun visitPrimitiveType(primitiveType: PsiPrimitiveType): PsiType? {
+                return primitiveType
+            }
+
+            override fun visitEllipsisType(ellipsisType: PsiEllipsisType): PsiType? {
+                ellipsisType.componentType.accept(this)
+                return ellipsisType
+            }
+
+            override fun visitArrayType(arrayType: PsiArrayType): PsiType? {
+                arrayType.componentType.accept(this)
+                return arrayType
+            }
+
+            override fun visitLambdaExpressionType(lambdaExpressionType: PsiLambdaExpressionType): PsiType? {
+                for (superType in lambdaExpressionType.superTypes) {
+                    superType.accept(this)
+                }
+                return lambdaExpressionType
+            }
+
+            override fun visitCapturedWildcardType(capturedWildcardType: PsiCapturedWildcardType): PsiType? {
+                capturedWildcardType.upperBound.accept(this)
+                return capturedWildcardType
+            }
+
+            override fun visitDisjunctionType(disjunctionType: PsiDisjunctionType): PsiType? {
+                for (type in disjunctionType.disjunctions) {
+                    type.accept(this)
+                }
+                return disjunctionType
+            }
+
+            override fun visitIntersectionType(intersectionType: PsiIntersectionType): PsiType? {
+                for (type in intersectionType.conjuncts) {
+                    type.accept(this)
+                }
+                return intersectionType
+            }
+        })
+
+        return classes
+    }
+
+    override fun convertType(replacementMap: Map<String, String>?, owner: Item?): TypeItem {
+        val s = convertTypeString(replacementMap)
+        return create(codebase, codebase.createPsiType(s, owner?.psi()))
+    }
+
+    override fun hasTypeArguments(): Boolean = psiType is PsiClassType && psiType.hasParameters()
+
+    companion object {
+        private fun getPrimitiveSignature(typeName: String): String? = when (typeName) {
+            "boolean" -> "Z"
+            "byte" -> "B"
+            "char" -> "C"
+            "short" -> "S"
+            "int" -> "I"
+            "long" -> "J"
+            "float" -> "F"
+            "double" -> "D"
+            "void" -> "V"
+            else -> null
+        }
+
+        private fun appendJvmSignature(
+            buffer: StringBuilder,
+            type: PsiType?
+        ): Boolean {
+            if (type == null) {
+                return false
+            }
+
+            val psiType = TypeConversionUtil.erasure(type)
+
+            when (psiType) {
+                is PsiArrayType -> {
+                    buffer.append('[')
+                    appendJvmSignature(buffer, psiType.componentType)
+                }
+                is PsiClassType -> {
+                    val resolved = psiType.resolve() ?: return false
+                    if (!appendJvmTypeName(buffer, resolved)) {
+                        return false
+                    }
+                }
+                is PsiPrimitiveType -> buffer.append(getPrimitiveSignature(psiType.canonicalText))
+                else -> return false
+            }
+            return true
+        }
+
+        private fun appendJvmTypeName(
+            signature: StringBuilder,
+            outerClass: PsiClass
+        ): Boolean {
+            val className = LintUtils.getInternalName(outerClass) ?: return false
+            signature.append('L').append(className).append(';')
+            return true
+        }
+
+        fun toTypeString(
+            codebase: Codebase,
+            type: PsiType,
+            outerAnnotations: Boolean,
+            innerAnnotations: Boolean,
+            erased: Boolean
+        ): String {
+
+            if (erased) {
+                // Recurse with raw type and erase=false
+                return toTypeString(
+                    codebase,
+                    TypeConversionUtil.erasure(type),
+                    outerAnnotations,
+                    innerAnnotations,
+                    false
+                )
+            }
+
+            if (outerAnnotations || innerAnnotations) {
+                val typeString = mapAnnotations(codebase, getCanonicalText(type, true))
+                if (!outerAnnotations && typeString.contains("@")) {
+                    // Temporary hack: should use PSI type visitor instead
+                    return TextTypeItem.eraseAnnotations(typeString, false, true)
+                }
+                return typeString
+
+            } else {
+                return type.canonicalText
+            }
+        }
+
+        /**
+         * Replace annotations in the given type string with the mapped qualified names
+         * to [AnnotationItem.mapName]
+         */
+        private fun mapAnnotations(codebase: Codebase, string: String): String {
+            var s = string
+            var offset = s.length
+            while (true) {
+                val start = s.lastIndexOf('@', offset)
+                if (start == -1) {
+                    return s
+                }
+                var index = start + 1
+                val length = string.length
+                while (index < length) {
+                    val c = string[index]
+                    if (c != '.' && !Character.isJavaIdentifierPart(c)) {
+                        break
+                    }
+                    index++
+                }
+                val annotation = string.substring(start + 1, index)
+
+                val mapped = AnnotationItem.mapName(codebase, annotation, ApiPredicate(codebase))
+                if (mapped != null) {
+                    if (mapped != annotation) {
+                        s = string.substring(0, start + 1) + mapped + s.substring(index)
+                    }
+                } else {
+                    var balance = 0
+                    // Find annotation end
+                    while (index < length) {
+                        val c = string[index]
+                        if (c == '(') {
+                            balance++
+                        } else if (c == ')') {
+                            balance--
+                            if (balance == 0) {
+                                index++
+                                break
+                            }
+                        } else if (c != ' ' && balance == 0) {
+                            break
+                        }
+                        index++
+                    }
+                    s = string.substring(0, start) + s.substring(index)
+                }
+                offset = start - 1
+            }
+        }
+
+        private fun getCanonicalText(type: PsiType, annotated: Boolean): String {
+            val typeString = type.getCanonicalText(annotated)
+            if (!annotated) {
+                return typeString
+            }
+
+            val index = typeString.indexOf(".@")
+            if (index != -1) {
+                // Work around type bugs in PSI: when you have a type like this:
+                //    @android.support.annotation.NonNull java.lang.Float)
+                // PSI returns
+                //    @android.support.annotation.NonNull java.lang.@android.support.annotation.NonNull Float)
+                //
+                //
+                // ...but sadly it's less predictable; e.g. it can be
+                //    java.util.List<@android.support.annotation.Nullable java.lang.String>
+                // PSI returns
+                //    java.util.List<java.lang.@android.support.annotation.Nullable String>
+
+                // Here we try to reverse this:
+                val end = typeString.indexOf(' ', index)
+                if (end != -1) {
+                    val annotation = typeString.substring(index + 1, end)
+                    if (typeString.lastIndexOf(annotation, index) == -1) {
+                        // Find out where to place it
+                        var ci = index
+                        while (ci > 0) {
+                            val c = typeString[ci]
+                            if (c != '.' && !Character.isJavaIdentifierPart(c)) {
+                                ci++
+                                break
+                            }
+                            ci--
+                        }
+                        return typeString.substring(0, ci) +
+                                annotation + " " +
+                                typeString.substring(ci, index + 1) +
+                                typeString.substring(end + 1)
+                    } else {
+                        return typeString.substring(0, index + 1) + typeString.substring(end + 1)
+                    }
+                }
+            }
+
+            return typeString
+        }
+
+        fun create(codebase: PsiBasedCodebase, psiType: PsiType): PsiTypeItem {
+            return PsiTypeItem(codebase, psiType)
+        }
+
+        fun create(codebase: PsiBasedCodebase, original: PsiTypeItem): PsiTypeItem {
+            return PsiTypeItem(codebase, original.psiType)
+        }
+
+        fun typeParameterList(typeList: PsiTypeParameterList?): String? {
+            if (typeList != null && typeList.typeParameters.isNotEmpty()) {
+                // TODO: Filter the type list classes? Try to construct a typelist of a private API!
+                // We can't just use typeList.text here, because that just
+                // uses the declaration from the source, which may not be
+                // fully qualified - e.g. we might get
+                //    <T extends View> instead of <T extends android.view.View>
+                // Therefore, we'll need to compute it ourselves; I can't find
+                // a utility for this
+                val sb = StringBuilder()
+                typeList.accept(object : PsiRecursiveElementVisitor() {
+                    override fun visitElement(element: PsiElement) {
+                        if (element is PsiTypeParameterList) {
+                            val typeParameters = element.typeParameters
+                            if (typeParameters.isEmpty()) {
+                                return
+                            }
+                            sb.append("<")
+                            var first = true
+                            for (parameter in typeParameters) {
+                                if (!first) {
+                                    sb.append(", ")
+                                }
+                                first = false
+                                visitElement(parameter)
+                            }
+                            sb.append(">")
+                            return
+                        } else if (element is PsiTypeParameter) {
+                            sb.append(element.name)
+                            // TODO: How do I get super -- e.g. "Comparable<? super T>"
+                            val extendsList = element.extendsList
+                            val refList = extendsList.referenceElements
+                            if (refList.isNotEmpty()) {
+                                sb.append(" extends ")
+                                var first = true
+                                for (refElement in refList) {
+                                    if (!first) {
+                                        sb.append(" & ")
+                                    } else {
+                                        first = false
+                                    }
+
+                                    if (refElement is PsiJavaCodeReferenceElement) {
+                                        visitElement(refElement)
+                                        continue
+                                    }
+                                    val resolved = refElement.resolve()
+                                    if (resolved is PsiClass) {
+                                        sb.append(resolved.qualifiedName ?: resolved.name)
+                                        resolved.typeParameterList?.accept(this)
+                                    } else {
+                                        sb.append(refElement.referenceName)
+                                    }
+                                }
+                            } else {
+                                val extendsListTypes = element.extendsListTypes
+                                if (extendsListTypes.isNotEmpty()) {
+                                    sb.append(" extends ")
+                                    var first = true
+                                    for (type in extendsListTypes) {
+                                        if (!first) {
+                                            sb.append(" & ")
+                                        } else {
+                                            first = false
+                                        }
+                                        val resolved = type.resolve()
+                                        if (resolved == null) {
+                                            sb.append(type.className)
+                                        } else {
+                                            sb.append(resolved.qualifiedName ?: resolved.name)
+                                            resolved.typeParameterList?.accept(this)
+                                        }
+                                    }
+                                }
+                            }
+                            return
+                        } else if (element is PsiJavaCodeReferenceElement) {
+                            val resolved = element.resolve()
+                            if (resolved is PsiClass) {
+                                if (resolved.qualifiedName == null) {
+                                    sb.append(resolved.name)
+                                } else {
+                                    sb.append(resolved.qualifiedName)
+                                }
+                                val typeParameters = element.parameterList
+                                if (typeParameters != null) {
+                                    val typeParameterElements = typeParameters.typeParameterElements
+                                    if (typeParameterElements.isEmpty()) {
+                                        return
+                                    }
+
+                                    // When reading in this from bytecode, the order is sometimes wrong
+                                    // (for example, for
+                                    //    public interface BaseStream<T, S extends BaseStream<T, S>>
+                                    // the extends type BaseStream<T, S> will return the typeParameterElements
+                                    // as [S,T] instead of [T,S]. However, the typeParameters.typeArguments
+                                    // list is correct, so order the elements by the typeArguments array instead
+
+                                    // Special case: just one type argument: no sorting issue
+                                    if (typeParameterElements.size == 1) {
+                                        sb.append("<")
+                                        var first = true
+                                        for (parameter in typeParameterElements) {
+                                            if (!first) {
+                                                sb.append(", ")
+                                            }
+                                            first = false
+                                            visitElement(parameter)
+                                        }
+                                        sb.append(">")
+                                        return
+                                    }
+
+                                    // More than one type argument
+
+                                    val typeArguments = typeParameters.typeArguments
+                                    if (typeArguments.isNotEmpty()) {
+                                        sb.append("<")
+                                        var first = true
+                                        for (parameter in typeArguments) {
+                                            if (!first) {
+                                                sb.append(", ")
+                                            }
+                                            first = false
+                                            // Try to match up a type parameter element
+                                            var found = false
+                                            for (typeElement in typeParameterElements) {
+                                                if (parameter == typeElement.type) {
+                                                    found = true
+                                                    visitElement(typeElement)
+                                                    break
+                                                }
+                                            }
+                                            if (!found) {
+                                                // No type element matched: use type instead
+                                                val classType = PsiTypesUtil.getPsiClass(parameter)
+                                                if (classType != null) {
+                                                    visitElement(classType)
+                                                } else {
+                                                    sb.append(parameter.canonicalText)
+                                                }
+                                            }
+                                        }
+                                        sb.append(">")
+                                    }
+                                }
+                                return
+                            }
+                        } else if (element is PsiTypeElement) {
+                            val type = element.type
+                            if (type is PsiWildcardType) {
+                                sb.append("?")
+                                if (type.isBounded) {
+                                    if (type.isExtends) {
+                                        sb.append(" extends ")
+                                        sb.append(type.extendsBound.canonicalText)
+                                    }
+                                    if (type.isSuper) {
+                                        sb.append(" super ")
+                                        sb.append(type.superBound.canonicalText)
+                                    }
+                                }
+                                return
+                            }
+                            sb.append(type.canonicalText)
+                            return
+                        } else if (element is PsiJavaToken && element.tokenType == JavaTokenType.COMMA) {
+                            sb.append(",")
+                            if (compatibility.spaceAfterCommaInTypes) {
+                                if (element.nextSibling == null || element.nextSibling !is PsiWhiteSpace) {
+                                    sb.append(" ")
+                                }
+                            }
+                            return
+                        }
+                        if (element.firstChild == null) { // leaf nodes only
+                            if (element is PsiCompiledElement) {
+                                if (element is PsiReferenceList) {
+                                    val referencedTypes = element.referencedTypes
+                                    var first = true
+                                    for (referenceType in referencedTypes) {
+                                        if (first) {
+                                            first = false
+                                        } else {
+                                            sb.append(", ")
+                                        }
+                                        sb.append(referenceType.canonicalText)
+                                    }
+                                }
+                            } else {
+                                sb.append(element.text)
+                            }
+                        }
+                        super.visitElement(element)
+                    }
+                })
+
+                val typeString = sb.toString()
+                return TypeItem.cleanupGenerics(typeString)
+            }
+
+            return null
+        }
+
+        fun typeParameterClasses(codebase: PsiBasedCodebase, typeList: PsiTypeParameterList?): List<ClassItem> {
+            if (typeList != null && typeList.typeParameters.isNotEmpty()) {
+                val list = mutableListOf<ClassItem>()
+                typeList.accept(object : PsiRecursiveElementVisitor() {
+                    override fun visitElement(element: PsiElement) {
+                        if (element is PsiTypeParameterList) {
+                            val typeParameters = element.typeParameters
+                            for (parameter in typeParameters) {
+                                visitElement(parameter)
+                            }
+                            return
+                        } else if (element is PsiTypeParameter) {
+                            val extendsList = element.extendsList
+                            val refList = extendsList.referenceElements
+                            if (refList.isNotEmpty()) {
+                                for (refElement in refList) {
+                                    if (refElement is PsiJavaCodeReferenceElement) {
+                                        visitElement(refElement)
+                                        continue
+                                    }
+                                    val resolved = refElement.resolve()
+                                    if (resolved is PsiClass) {
+                                        addRealClass(
+                                            list,
+                                            codebase.findOrCreateClass(resolved)
+                                        )
+                                        resolved.typeParameterList?.accept(this)
+                                    }
+                                }
+                            } else {
+                                val extendsListTypes = element.extendsListTypes
+                                if (extendsListTypes.isNotEmpty()) {
+                                    for (type in extendsListTypes) {
+                                        val resolved = type.resolve()
+                                        if (resolved != null) {
+                                            addRealClass(
+                                                list, codebase.findOrCreateClass(resolved)
+                                            )
+                                            resolved.typeParameterList?.accept(this)
+                                        }
+                                    }
+                                }
+                            }
+                            return
+                        } else if (element is PsiJavaCodeReferenceElement) {
+                            val resolved = element.resolve()
+                            if (resolved is PsiClass) {
+                                addRealClass(
+                                    list,
+                                    codebase.findOrCreateClass(resolved)
+                                )
+                                element.parameterList?.accept(this)
+                                return
+                            }
+                        } else if (element is PsiTypeElement) {
+                            val type = element.type
+                            if (type is PsiWildcardType) {
+                                if (type.isBounded) {
+                                    addRealClass(
+                                        codebase,
+                                        list, type.bound
+                                    )
+                                }
+                                if (type.isExtends) {
+                                    addRealClass(
+                                        codebase,
+                                        list, type.extendsBound
+                                    )
+                                }
+                                if (type.isSuper) {
+                                    addRealClass(
+                                        codebase,
+                                        list, type.superBound
+                                    )
+                                }
+                                return
+                            }
+                            return
+                        }
+                        super.visitElement(element)
+                    }
+                })
+
+                return list
+            } else {
+                return emptyList()
+            }
+        }
+
+        private fun addRealClass(codebase: PsiBasedCodebase, classes: MutableList<ClassItem>, type: PsiType?) {
+            codebase.findClass(type ?: return)?.let {
+                addRealClass(classes, it)
+            }
+        }
+
+        private fun addRealClass(classes: MutableList<ClassItem>, cls: ClassItem) {
+            if (!cls.isTypeParameter && !classes.contains(cls)) { // typically small number of items, don't need Set
+                classes.add(cls)
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/model/text/TextBackedAnnotationItem.kt b/src/main/java/com/android/tools/metalava/model/text/TextBackedAnnotationItem.kt
new file mode 100644
index 0000000..4a8b138
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/model/text/TextBackedAnnotationItem.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava.model.text
+
+import com.android.tools.metalava.model.AnnotationAttribute
+import com.android.tools.metalava.model.AnnotationItem
+import com.android.tools.metalava.model.Codebase
+import com.android.tools.metalava.model.DefaultAnnotationAttribute
+
+class TextBackedAnnotationItem(
+    override val codebase: Codebase,
+    source: String,
+    mapName: Boolean = true
+) : AnnotationItem {
+    private val qualifiedName: String?
+    private val full: String
+    private val attributes: List<AnnotationAttribute>
+
+    init {
+        val index = source.indexOf("(")
+        val annotationClass = if (index == -1)
+            source.substring(1) // Strip @
+        else
+            source.substring(1, index)
+
+        qualifiedName = if (mapName) AnnotationItem.mapName(codebase, annotationClass) else annotationClass
+        full = when {
+            qualifiedName == null -> ""
+            index == -1 -> "@" + qualifiedName
+            else -> "@" + qualifiedName + source.substring(index)
+        }
+
+        attributes = if (index == -1) {
+            emptyList()
+        } else {
+            DefaultAnnotationAttribute.createList(
+                source.substring(index + 1, source.lastIndexOf(')'))
+            )
+        }
+    }
+
+    override fun qualifiedName(): String? = qualifiedName
+    override fun attributes(): List<AnnotationAttribute> = attributes
+    override fun toSource(): String = full
+}
diff --git a/src/main/java/com/android/tools/metalava/model/text/TextClassItem.kt b/src/main/java/com/android/tools/metalava/model/text/TextClassItem.kt
new file mode 100644
index 0000000..e677b24
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/model/text/TextClassItem.kt
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava.model.text
+
+import com.android.tools.metalava.doclava1.ApiInfo
+import com.android.tools.metalava.doclava1.SourcePositionInfo
+import com.android.tools.metalava.model.ClassItem
+import com.android.tools.metalava.model.Codebase
+import com.android.tools.metalava.model.ConstructorItem
+import com.android.tools.metalava.model.FieldItem
+import com.android.tools.metalava.model.Item
+import com.android.tools.metalava.model.MethodItem
+import com.android.tools.metalava.model.PackageItem
+import com.android.tools.metalava.model.TypeItem
+import java.util.function.Predicate
+
+class TextClassItem(
+    override val codebase: ApiInfo,
+    position: SourcePositionInfo = SourcePositionInfo.UNKNOWN,
+    isPublic: Boolean = false,
+    isProtected: Boolean = false,
+    isPrivate: Boolean = false,
+    isStatic: Boolean = false,
+    private var isInterface: Boolean = false,
+    isAbstract: Boolean = false,
+    private var isEnum: Boolean = false,
+    private var isAnnotation: Boolean = false,
+    isFinal: Boolean = false,
+    val qualifiedName: String = "",
+    private val qualifiedTypeName: String = qualifiedName,
+    var name: String = qualifiedName.substring(qualifiedName.lastIndexOf('.') + 1),
+    val annotations: List<String>? = null
+) : TextItem(
+    codebase = codebase,
+    position = position,
+    modifiers = TextModifiers(
+        codebase = codebase,
+        annotationStrings = annotations,
+        public = isPublic, protected = isProtected, private = isPrivate,
+        static = isStatic, abstract = isAbstract, final = isFinal
+    )
+), ClassItem {
+
+    init {
+        (modifiers as TextModifiers).owner = this
+    }
+
+    override val isTypeParameter: Boolean = false
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is ClassItem) return false
+
+        return qualifiedName == other.qualifiedName()
+    }
+
+    override fun hashCode(): Int {
+        return qualifiedName.hashCode()
+    }
+
+    override fun interfaceTypes(): List<TypeItem> = interfaceTypes
+    override fun allInterfaces(): Sequence<ClassItem> {
+        return interfaceTypes.asSequence().map { it.asClass() }.filterNotNull()
+    }
+
+    private var innerClasses: List<ClassItem> = mutableListOf()
+
+    override var defaultConstructor: ConstructorItem? = null
+
+    override var hasPrivateConstructor: Boolean = false
+
+    override fun innerClasses(): List<ClassItem> = innerClasses
+
+    override fun hasImplicitDefaultConstructor(): Boolean {
+        return false
+    }
+
+    override fun isInterface(): Boolean = isInterface
+    override fun isAnnotationType(): Boolean = isAnnotation
+    override fun isEnum(): Boolean = isEnum
+
+    var containingClass: TextClassItem? = null
+    override fun containingClass(): ClassItem? = containingClass
+
+    private var containingPackage: PackageItem? = null
+
+    fun setContainingPackage(containingPackage: TextPackageItem) {
+        this.containingPackage = containingPackage
+    }
+
+    fun setIsAnnotationType(isAnnotation: Boolean) {
+        this.isAnnotation = isAnnotation
+    }
+
+    fun setIsEnum(isEnum: Boolean) {
+        this.isEnum = isEnum
+    }
+
+    override fun containingPackage(): PackageItem = containingPackage ?: error(this)
+
+    override fun toType(): TypeItem = codebase.obtainTypeFromString(
+// TODO: No, handle List<String>[]
+        if (typeParameterList() != null)
+            qualifiedName() + "<" + typeParameterList() + ">"
+        else
+            qualifiedName()
+    )
+
+    override fun hasTypeVariables(): Boolean {
+        return typeInfo?.hasTypeArguments() ?: false
+    }
+
+    override fun typeParameterList(): String? {
+// TODO: No, handle List<String>[]
+        val s = typeInfo.toString()
+        val index = s.indexOf('<')
+        if (index != -1) {
+            return s.substring(index)
+        }
+        return null
+    }
+
+    override fun typeParameterNames(): List<String> = codebase.unsupported()
+
+    private var superClass: ClassItem? = null
+    private var superClassType: TypeItem? = null
+
+    override fun superClass(): ClassItem? = superClass
+    override fun superClassType(): TypeItem? = superClassType
+
+    override fun setSuperClass(superClass: ClassItem?, superClassType: TypeItem?) {
+        this.superClass = superClass
+        this.superClassType = superClassType
+    }
+
+    override fun setInterfaceTypes(interfaceTypes: List<TypeItem>) {
+        this.interfaceTypes = interfaceTypes.toMutableList()
+    }
+
+    override fun findMethod(methodName: String, parameters: String): MethodItem? = codebase.unsupported()
+
+    override fun findField(fieldName: String): FieldItem? = codebase.unsupported()
+
+    private var typeInfo: TextTypeItem? = null
+    fun setTypeInfo(typeInfo: TextTypeItem) {
+        this.typeInfo = typeInfo
+    }
+
+    fun asTypeInfo(): TextTypeItem {
+        if (typeInfo == null) {
+            typeInfo = codebase.obtainTypeFromString(qualifiedTypeName)
+        }
+        return typeInfo!!
+    }
+
+    private var interfaceTypes = mutableListOf<TypeItem>()
+    private val constructors = mutableListOf<ConstructorItem>()
+    private val methods = mutableListOf<MethodItem>()
+    private val fields = mutableListOf<FieldItem>()
+
+    override fun constructors(): List<ConstructorItem> = constructors
+    override fun methods(): List<MethodItem> = methods
+    override fun fields(): List<FieldItem> = fields
+
+    fun addInterface(intf: TypeItem) {
+        interfaceTypes.add(intf)
+    }
+
+    fun addInterface(intf: TextClassItem) {
+        interfaceTypes.add(intf.toType())
+    }
+
+    fun addConstructor(constructor: TextConstructorItem) {
+        constructors += constructor
+    }
+
+    fun addMethod(method: TextMethodItem) {
+        methods += method
+    }
+
+    fun addField(field: TextFieldItem) {
+        fields += field
+    }
+
+    fun addEnumConstant(field: TextFieldItem) {
+        field.setEnumConstant(true)
+        fields += field
+    }
+
+    fun addInnerClass(cls: TextClassItem) {
+        innerClasses += cls
+    }
+
+    override fun filteredSuperClassType(predicate: Predicate<Item>): TypeItem? {
+        // No filtering in signature files: we assume signature APIs
+        // have already been filtered and all items should match.
+        // This lets us load signature files and rewrite them using updated
+        // output formats etc.
+        return superClassType
+    }
+
+    private var fullName: String = name
+    override fun simpleName(): String = name.substring(name.lastIndexOf('.') + 1)
+    override fun fullName(): String = fullName
+    override fun qualifiedName(): String = qualifiedName
+    override fun toString(): String = qualifiedName()
+
+    companion object {
+        fun createClassStub(codebase: ApiInfo, name: String): TextClassItem =
+            TextClassItem(codebase = codebase, qualifiedName = name, isPublic = true).also {
+                addStubPackage(
+                    name,
+                    codebase,
+                    it
+                )
+            }
+
+        private fun addStubPackage(
+            name: String, codebase: Codebase,
+            textClassItem: TextClassItem
+        ) {
+            val pkgPath = name.substring(0, name.lastIndexOf('.'))
+            val pkg = codebase.findPackage(pkgPath) as? TextPackageItem ?: TextPackageItem(
+                codebase,
+                pkgPath,
+                SourcePositionInfo.UNKNOWN
+            )
+            textClassItem.setContainingPackage(pkg)
+        }
+
+        fun createInterfaceStub(codebase: ApiInfo, name: String): TextClassItem =
+            TextClassItem(isInterface = true, codebase = codebase, qualifiedName = name, isPublic = true).also {
+                addStubPackage(
+                    name,
+                    codebase,
+                    it
+                )
+            }
+    }
+}
diff --git a/src/main/java/com/android/tools/metalava/model/text/TextConstructorItem.kt b/src/main/java/com/android/tools/metalava/model/text/TextConstructorItem.kt
new file mode 100644
index 0000000..5dd2c02
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/model/text/TextConstructorItem.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava.model.text
+
+import com.android.tools.metalava.doclava1.SourcePositionInfo
+import com.android.tools.metalava.model.Codebase
+import com.android.tools.metalava.model.ConstructorItem
+
+class TextConstructorItem(
+    codebase: Codebase,
+    name: String,
+    containingClass: TextClassItem,
+    isPublic: Boolean,
+    isProtected: Boolean,
+    isPrivate: Boolean,
+    isFinal: Boolean,
+    isStatic: Boolean,
+    isAbstract: Boolean,
+    isSynchronized: Boolean,
+    isNative: Boolean,
+    isDefault: Boolean,
+    returnType: TextTypeItem?,
+    position: SourcePositionInfo,
+    annotations: List<String>?
+) : TextMethodItem(
+    codebase, name, containingClass, isPublic, isProtected, isPrivate,
+    isFinal, isStatic, isAbstract, isSynchronized, isNative, isDefault, returnType, position, annotations
+),
+    ConstructorItem {
+    override var superConstructor: ConstructorItem? = null
+
+    override fun isConstructor(): Boolean = true
+}
+
+
diff --git a/src/main/java/com/android/tools/metalava/model/text/TextFieldItem.kt b/src/main/java/com/android/tools/metalava/model/text/TextFieldItem.kt
new file mode 100644
index 0000000..0180cd9
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/model/text/TextFieldItem.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava.model.text
+
+import com.android.tools.metalava.doclava1.SourcePositionInfo
+import com.android.tools.metalava.model.ClassItem
+import com.android.tools.metalava.model.Codebase
+import com.android.tools.metalava.model.FieldItem
+import com.android.tools.metalava.model.TypeItem
+
+class TextFieldItem(
+    codebase: Codebase,
+    name: String,
+    containingClass: TextClassItem,
+    isPublic: Boolean,
+    isProtected: Boolean,
+    isPrivate: Boolean,
+    isFinal: Boolean, isStatic: Boolean,
+    isTransient: Boolean,
+    isVolatile: Boolean,
+    private val type: TextTypeItem,
+    private val constantValue: Any?,
+    position: SourcePositionInfo,
+    annotations: List<String>?
+) : TextMemberItem(
+    codebase, name, containingClass, position,
+    TextModifiers(
+        codebase = codebase,
+        annotationStrings = annotations,
+        public = isPublic, protected = isProtected, private = isPrivate,
+        static = isStatic, final = isFinal, transient = isTransient, volatile = isVolatile
+    )
+), FieldItem {
+
+    init {
+        (modifiers as TextModifiers).owner = this
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is FieldItem) return false
+
+        if (name() != other.name()) {
+            return false
+        }
+
+        return containingClass() == other.containingClass()
+    }
+
+    override fun hashCode(): Int = name().hashCode()
+
+    override fun type(): TypeItem = type
+
+    override fun initialValue(requireConstant: Boolean): Any? = constantValue
+
+    override fun toString(): String = "Field ${containingClass().fullName()}.${name()}"
+
+    override fun duplicate(targetContainingClass: ClassItem): FieldItem = codebase.unsupported()
+
+    private var isEnumConstant = false
+    override fun isEnumConstant(): Boolean = isEnumConstant
+    fun setEnumConstant(isEnumConstant: Boolean) {
+        this.isEnumConstant = isEnumConstant
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/model/text/TextItem.kt b/src/main/java/com/android/tools/metalava/model/text/TextItem.kt
new file mode 100644
index 0000000..5caea45
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/model/text/TextItem.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava.model.text
+
+import com.android.tools.metalava.doclava1.SourcePositionInfo
+import com.android.tools.metalava.model.Codebase
+import com.android.tools.metalava.model.DefaultItem
+import com.android.tools.metalava.model.ModifierList
+import com.android.tools.metalava.model.MutableModifierList
+
+abstract class TextItem(
+    override val codebase: Codebase,
+    val position: SourcePositionInfo,
+    override var docOnly: Boolean = false,
+    override var documentation: String = "",
+    override var modifiers: ModifierList
+) : DefaultItem() {
+
+    override var hidden = false
+    override var removed = false
+
+    override fun findTagDocumentation(tag: String): String? = null
+    override fun appendDocumentation(comment: String, tagSection: String?, append: Boolean) = codebase.unsupported()
+    override fun mutableModifiers(): MutableModifierList = modifiers as MutableModifierList
+
+    private var mutableDeprecated = false
+    override val deprecated
+        get() = mutableDeprecated
+
+    fun setDeprecated(deprecated: Boolean) {
+        mutableDeprecated = deprecated
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/model/text/TextMemberItem.kt b/src/main/java/com/android/tools/metalava/model/text/TextMemberItem.kt
new file mode 100644
index 0000000..71f97b4
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/model/text/TextMemberItem.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava.model.text
+
+import com.android.tools.metalava.doclava1.SourcePositionInfo
+import com.android.tools.metalava.model.ClassItem
+import com.android.tools.metalava.model.Codebase
+import com.android.tools.metalava.model.MemberItem
+import com.android.tools.metalava.model.ModifierList
+
+abstract class TextMemberItem(
+    codebase: Codebase,
+    private val name: String,
+    private val containingClass: TextClassItem,
+    position: SourcePositionInfo,
+    override var modifiers: ModifierList
+) : TextItem(codebase, position = position, modifiers = modifiers), MemberItem {
+
+    override fun name(): String = name
+    override fun containingClass(): ClassItem = containingClass
+}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/model/text/TextMethodItem.kt b/src/main/java/com/android/tools/metalava/model/text/TextMethodItem.kt
new file mode 100644
index 0000000..46c26a0
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/model/text/TextMethodItem.kt
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava.model.text
+
+import com.android.tools.metalava.doclava1.SourcePositionInfo
+import com.android.tools.metalava.model.ClassItem
+import com.android.tools.metalava.model.Codebase
+import com.android.tools.metalava.model.Item
+import com.android.tools.metalava.model.MethodItem
+import com.android.tools.metalava.model.ParameterItem
+import com.android.tools.metalava.model.TypeItem
+import java.util.function.Predicate
+
+open class TextMethodItem(
+    codebase: Codebase,
+    name: String,
+    containingClass: TextClassItem,
+    isPublic: Boolean,
+    isProtected: Boolean,
+    isPrivate: Boolean,
+    isFinal: Boolean,
+    isStatic: Boolean,
+    isAbstract: Boolean,
+    isSynchronized: Boolean,
+    isNative: Boolean,
+    isDefault: Boolean,
+    private val returnType: TextTypeItem?,
+    position: SourcePositionInfo,
+    annotations: List<String>?
+) : TextMemberItem(
+    // Explicitly coerce 'final' state of Java6-compiled enum values() method, to match
+    // the Java5-emitted base API description.
+    codebase, name, containingClass, position,
+    modifiers = TextModifiers(
+        codebase = codebase,
+        annotationStrings = annotations, public = isPublic, protected = isProtected,
+        private = isPrivate, static = isStatic, final = isFinal, abstract = isAbstract,
+        synchronized = isSynchronized, native = isNative, default = isDefault
+    )
+), MethodItem {
+
+    init {
+        (modifiers as TextModifiers).owner = this
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is MethodItem) return false
+
+        if (name() != other.name()) {
+            return false
+        }
+
+        if (containingClass() != other.containingClass()) {
+            return false
+        }
+
+        val parameters1 = parameters()
+        val parameters2 = other.parameters()
+
+        if (parameters1.size != parameters2.size) {
+            return false
+        }
+
+        for (i in 0 until parameters1.size) {
+            val parameter1 = parameters1[i]
+            val parameter2 = parameters2[i]
+            if (parameter1.type() != parameter2.type()) {
+                return false
+            }
+        }
+        return true
+    }
+
+    override fun hashCode(): Int {
+        return name().hashCode()
+    }
+
+    override fun isConstructor(): Boolean = false
+
+    override fun returnType(): TypeItem? = returnType
+
+    override fun superMethods(): List<MethodItem> = codebase.unsupported()
+
+    override fun findPredicateSuperMethod(predicate: Predicate<Item>): MethodItem? = null
+
+    private var typeParameterList: String? = null
+
+    fun setTypeParameterList(typeParameterList: String?) {
+        this.typeParameterList = typeParameterList
+    }
+
+    override fun typeParameterList(): String? = typeParameterList
+
+    override fun duplicate(targetContainingClass: ClassItem): MethodItem = codebase.unsupported()
+
+    private val throwsTypes = mutableListOf<String>()
+    private val parameters = mutableListOf<TextParameterItem>()
+    private var throwsClasses: List<ClassItem>? = null
+
+    fun throwsTypeNames(): List<String> {
+        return throwsTypes
+    }
+
+    override fun throwsTypes(): List<ClassItem> = if (throwsClasses == null) emptyList() else throwsClasses!!
+
+    fun setThrowsList(throwsClasses: List<TextClassItem>) {
+        this.throwsClasses = throwsClasses
+    }
+
+    override fun parameters(): List<ParameterItem> = parameters
+
+    fun addException(throwsType: String) {
+        throwsTypes += throwsType
+    }
+
+    fun addParameter(parameter: TextParameterItem) {
+        parameters += parameter
+    }
+
+    private var varargs: Boolean = false
+
+    fun setVarargs(varargs: Boolean) {
+        this.varargs = varargs
+    }
+
+    fun isVarargs(): Boolean = varargs
+
+    override var inheritedInterfaceMethod: Boolean = false
+
+    override fun toString(): String =
+        "${if (isConstructor()) "Constructor" else "Method"} ${containingClass().qualifiedName()}.${name()}(${parameters().joinToString {
+            it.type().toSimpleType()
+        }})"
+}
+
+
diff --git a/src/main/java/com/android/tools/metalava/model/text/TextModifiers.kt b/src/main/java/com/android/tools/metalava/model/text/TextModifiers.kt
new file mode 100644
index 0000000..5f3e3dd
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/model/text/TextModifiers.kt
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava.model.text
+
+import com.android.tools.metalava.model.AnnotationAttribute
+import com.android.tools.metalava.model.AnnotationItem
+import com.android.tools.metalava.model.Codebase
+import com.android.tools.metalava.model.DefaultAnnotationAttribute
+import com.android.tools.metalava.model.Item
+import com.android.tools.metalava.model.ModifierList
+import com.android.tools.metalava.model.MutableModifierList
+import java.io.StringWriter
+
+class TextModifiers(
+    override val codebase: Codebase,
+    annotationStrings: List<String>? = null,
+    private var public: Boolean = false,
+    private var protected: Boolean = false,
+    private var private: Boolean = false,
+    private var static: Boolean = false,
+    private var abstract: Boolean = false,
+    private var final: Boolean = false,
+    private var native: Boolean = false,
+    private var synchronized: Boolean = false,
+    private var strictfp: Boolean = false,
+    private var transient: Boolean = false,
+    private var volatile: Boolean = false,
+    private var default: Boolean = false
+) : MutableModifierList {
+    private var annotations: MutableList<AnnotationItem> = mutableListOf()
+
+    init {
+        annotationStrings?.forEach { source ->
+            val index = source.indexOf('(')
+            val qualifiedName = AnnotationItem.mapName(
+                codebase,
+                if (index == -1) source.substring(1) else source.substring(1, index)
+            )
+
+            val attributes =
+                if (index == -1) {
+                    emptyList()
+                } else {
+                    DefaultAnnotationAttribute.createList(source.substring(index + 1, source.lastIndexOf(')')))
+                }
+            val codebase = codebase
+            val item = object : AnnotationItem {
+                override val codebase = codebase
+                override fun attributes(): List<AnnotationAttribute> = attributes
+                override fun qualifiedName(): String? = qualifiedName
+                override fun toSource(): String = source
+            }
+            annotations.add(item)
+        }
+    }
+
+    override fun isPublic(): Boolean = public
+    override fun isProtected(): Boolean = protected
+    override fun isPrivate(): Boolean = private
+    override fun isStatic(): Boolean = static
+    override fun isAbstract(): Boolean = abstract
+    override fun isFinal(): Boolean = final
+    override fun isNative(): Boolean = native
+    override fun isSynchronized(): Boolean = synchronized
+    override fun isStrictFp(): Boolean = strictfp
+    override fun isTransient(): Boolean = transient
+    override fun isVolatile(): Boolean = volatile
+    override fun isDefault(): Boolean = default
+
+    override fun setPublic(public: Boolean) {
+        this.public = public
+    }
+
+    override fun setProtected(protected: Boolean) {
+        this.protected = protected
+    }
+
+    override fun setPrivate(private: Boolean) {
+        this.private = private
+    }
+
+    override fun setStatic(static: Boolean) {
+        this.static = static
+    }
+
+    override fun setAbstract(abstract: Boolean) {
+        this.abstract = abstract
+    }
+
+    override fun setFinal(final: Boolean) {
+        this.final = final
+    }
+
+    override fun setNative(native: Boolean) {
+        this.native = native
+    }
+
+    override fun setSynchronized(synchronized: Boolean) {
+        this.synchronized = synchronized
+    }
+
+    override fun setStrictFp(strictfp: Boolean) {
+        this.strictfp = strictfp
+    }
+
+    override fun setTransient(transient: Boolean) {
+        this.transient = transient
+    }
+
+    override fun setVolatile(volatile: Boolean) {
+        this.volatile = volatile
+    }
+
+    override fun setDefault(default: Boolean) {
+        this.default = default
+    }
+
+    var owner: Item? = null
+
+    override fun owner(): Item = owner!! // Must be set after construction
+    override fun isEmpty(): Boolean {
+        return !(public || protected || private || static || abstract || final || native || synchronized
+                || strictfp || transient || volatile || default)
+    }
+
+    override fun annotations(): List<AnnotationItem> {
+        return annotations
+    }
+
+    override fun addAnnotation(annotation: AnnotationItem) {
+        val qualifiedName = annotation.qualifiedName()
+        if (annotations.any { it.qualifiedName() == qualifiedName }) {
+            return
+        }
+        // TODO: Worry about repeatable annotations?
+        annotations.add(annotation)
+    }
+
+    override fun removeAnnotation(annotation: AnnotationItem) {
+        annotations.remove(annotation)
+    }
+
+    override fun clearAnnotations(annotation: AnnotationItem) {
+        annotations.clear()
+    }
+
+    override fun toString(): String {
+        val item = owner ?: return super.toString()
+        val writer = StringWriter()
+        ModifierList.write(writer, this, item)
+        return writer.toString()
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/model/text/TextPackageItem.kt b/src/main/java/com/android/tools/metalava/model/text/TextPackageItem.kt
new file mode 100644
index 0000000..c8559e0
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/model/text/TextPackageItem.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava.model.text
+
+import com.android.tools.metalava.doclava1.SourcePositionInfo
+import com.android.tools.metalava.model.ClassItem
+import com.android.tools.metalava.model.Codebase
+import com.android.tools.metalava.model.PackageItem
+
+class TextPackageItem(
+    codebase: Codebase,
+    private val name: String,
+    position: SourcePositionInfo
+) : TextItem(codebase, position, modifiers = TextModifiers(codebase = codebase, public = true)), PackageItem {
+    init {
+        (modifiers as TextModifiers).owner = this
+    }
+
+    private val classes = ArrayList<TextClassItem>(100)
+
+    fun name() = name
+
+    fun addClass(classInfo: TextClassItem) {
+        classes.add(classInfo)
+    }
+
+    internal fun classList(): List<ClassItem> = classes
+
+    override fun topLevelClasses(): Sequence<ClassItem> = classes.asSequence()
+
+    override fun qualifiedName(): String = name
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is PackageItem) return false
+
+        return name == other.qualifiedName()
+    }
+
+    override fun hashCode(): Int {
+        return name.hashCode()
+    }
+
+    override fun toString(): String = name
+}
diff --git a/src/main/java/com/android/tools/metalava/model/text/TextParameterItem.kt b/src/main/java/com/android/tools/metalava/model/text/TextParameterItem.kt
new file mode 100644
index 0000000..fcf5531
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/model/text/TextParameterItem.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava.model.text
+
+import com.android.tools.metalava.doclava1.SourcePositionInfo
+import com.android.tools.metalava.model.Codebase
+import com.android.tools.metalava.model.MethodItem
+import com.android.tools.metalava.model.ParameterItem
+
+class TextParameterItem(
+    codebase: Codebase,
+    private val containingMethod: TextMethodItem,
+    private var name: String,
+    private var publicName: String?,
+    override val parameterIndex: Int,
+    var typeName: String,
+    private var type: TextTypeItem,
+    var vararg: Boolean,
+    position: SourcePositionInfo,
+    annotations: List<String>?
+)
+// TODO: We need to pass in parameter modifiers here (synchronized etc)
+    : TextItem(
+    codebase, position,
+    modifiers = TextModifiers(codebase = codebase, annotationStrings = annotations)
+), ParameterItem {
+
+    init {
+        (modifiers as TextModifiers).owner = this
+    }
+
+    override var included: Boolean = true
+    override fun type(): TextTypeItem = type
+    override fun name(): String = name
+    override fun publicName(): String? = publicName
+    override fun containingMethod(): MethodItem = containingMethod
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is ParameterItem) return false
+
+        return parameterIndex == other.parameterIndex
+    }
+
+    override fun hashCode(): Int = parameterIndex
+
+    override fun toString(): String = "parameter ${name()}"
+}
diff --git a/src/main/java/com/android/tools/metalava/model/text/TextTypeItem.kt b/src/main/java/com/android/tools/metalava/model/text/TextTypeItem.kt
new file mode 100644
index 0000000..94aa3bd
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/model/text/TextTypeItem.kt
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava.model.text
+
+import com.android.tools.metalava.model.ClassItem
+import com.android.tools.metalava.model.Codebase
+import com.android.tools.metalava.model.Item
+import com.android.tools.metalava.model.TypeItem
+
+class TextTypeItem(
+    val codebase: Codebase,
+    val type: String
+) : TypeItem {
+    override fun toString(): String = type
+
+    override fun toErasedTypeString(): String {
+        return toTypeString(false, false, true)
+    }
+
+    override fun toTypeString(
+        outerAnnotations: Boolean,
+        innerAnnotations: Boolean,
+        erased: Boolean
+    ): String {
+        return Companion.toTypeString(type, outerAnnotations, innerAnnotations, erased)
+    }
+
+    override fun asClass(): ClassItem? {
+        val cls = toErasedTypeString()
+        return codebase.findClass(cls)
+    }
+
+    fun qualifiedTypeName(): String = type
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is TextTypeItem) return false
+
+        return qualifiedTypeName() == other.qualifiedTypeName()
+    }
+
+    override fun hashCode(): Int {
+        return qualifiedTypeName().hashCode()
+    }
+
+    override val primitive: Boolean
+        get() = isPrimitive(type)
+
+    override fun typeArgumentClasses(): List<ClassItem> = codebase.unsupported()
+
+    override fun convertType(replacementMap: Map<String, String>?, owner: Item?): TypeItem {
+        return TextTypeItem(codebase, convertTypeString(replacementMap))
+    }
+
+    companion object {
+        fun toTypeString(
+            type: String,
+            outerAnnotations: Boolean,
+            innerAnnotations: Boolean,
+            erased: Boolean
+        ): String {
+            return if (erased) {
+                val raw = eraseTypeArguments(type)
+                if (outerAnnotations && innerAnnotations) {
+                    raw
+                } else {
+                    eraseAnnotations(raw, outerAnnotations, innerAnnotations)
+                }
+            } else {
+                if (outerAnnotations && innerAnnotations) {
+                    type
+                } else {
+                    eraseAnnotations(type, outerAnnotations, innerAnnotations)
+                }
+            }
+        }
+
+        private fun eraseTypeArguments(s: String): String {
+            val index = s.indexOf('<')
+            if (index != -1) {
+                return s.substring(0, index)
+            }
+            return s
+        }
+
+        fun eraseAnnotations(type: String, outer: Boolean, inner: Boolean): String {
+            if (type.indexOf('@') == -1) {
+                return type
+            }
+
+            assert(inner || !outer) // Can't supply outer=true,inner=false
+
+            // Assumption: top level annotations appear first
+            val length = type.length
+            var max = if (!inner)
+                length
+            else {
+                val space = type.indexOf(' ')
+                val generics = type.indexOf('<')
+                val first = if (space != -1) {
+                    if (generics != -1) {
+                        Math.min(space, generics)
+                    } else {
+                        space
+                    }
+                } else {
+                    generics
+                }
+                if (first != -1) {
+                    first
+                } else {
+                    length
+                }
+            }
+
+            var s = type
+            while (true) {
+                val index = s.indexOf('@')
+                if (index == -1 || index >= max) {
+                    return s
+                }
+
+                // Find end
+                val end = findAnnotationEnd(s, index + 1)
+                val oldLength = s.length
+                s = s.substring(0, index).trim() + s.substring(end).trim()
+                val newLength = s.length
+                val removed = oldLength - newLength
+                max -= removed
+            }
+        }
+
+        private fun findAnnotationEnd(type: String, start: Int): Int {
+            var index = start
+            val length = type.length
+            var balance = 0
+            while (index < length) {
+                val c = type[index]
+                if (c == '(') {
+                    balance++
+                } else if (c == ')') {
+                    balance--
+                    if (balance == 0) {
+                        return index + 1
+                    }
+                } else if (c == '.') {
+                } else if (Character.isJavaIdentifierPart(c)) {
+                } else if (balance == 0) {
+                    break
+                }
+                index++
+            }
+            return index
+        }
+
+        fun isPrimitive(type: String): Boolean {
+            return when (type) {
+                "byte", "char", "double", "float", "int", "long", "short", "boolean", "void", "null" -> true
+                else -> false
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/model/visitors/ApiVisitor.kt b/src/main/java/com/android/tools/metalava/model/visitors/ApiVisitor.kt
new file mode 100644
index 0000000..3ee9389
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/model/visitors/ApiVisitor.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava.model.visitors
+
+import com.android.tools.metalava.doclava1.ApiPredicate
+import com.android.tools.metalava.model.ClassItem
+import com.android.tools.metalava.model.Codebase
+import com.android.tools.metalava.model.FieldItem
+import com.android.tools.metalava.model.Item
+import com.android.tools.metalava.model.MethodItem
+import java.util.function.Predicate
+
+open class ApiVisitor(
+    /**
+     * Whether constructors should be visited as part of a [#visitMethod] call
+     * instead of just a [#visitConstructor] call. Helps simplify visitors that
+     * don't care to distinguish between the two cases. Defaults to true.
+     */
+    visitConstructorsAsMethods: Boolean = true,
+    /**
+     * Whether inner classes should be visited "inside" a class; when this property
+     * is true, inner classes are visited before the [#afterVisitClass] method is
+     * called; when false, it's done afterwards. Defaults to false.
+     */
+    nestInnerClasses: Boolean = false,
+
+    /**
+     * Whether to include inherited fields too
+     */
+    val inlineInheritedFields: Boolean = true,
+
+    /** Comparator to sort methods with, or null to use natural (source) order */
+    val methodComparator: Comparator<MethodItem>? = null,
+
+    /** Comparator to sort fields with, or null to use natural (source) order */
+    val fieldComparator: Comparator<FieldItem>? = null,
+
+    /** The filter to use to determine if we should emit an item */
+    val filterEmit: Predicate<Item>,
+    /** The filter to use to determine if we should emit a reference to an item */
+    val filterReference: Predicate<Item>
+
+) : ItemVisitor(visitConstructorsAsMethods, nestInnerClasses) {
+    constructor(
+        codebase: Codebase,
+        /**
+         * Whether constructors should be visited as part of a [#visitMethod] call
+         * instead of just a [#visitConstructor] call. Helps simplify visitors that
+         * don't care to distinguish between the two cases. Defaults to true.
+         */
+        visitConstructorsAsMethods: Boolean = true,
+        /**
+         * Whether inner classes should be visited "inside" a class; when this property
+         * is true, inner classes are visited before the [#afterVisitClass] method is
+         * called; when false, it's done afterwards. Defaults to false.
+         */
+        nestInnerClasses: Boolean = false,
+
+        /** Whether to ignore APIs with annotations in the --show-annotations list */
+        ignoreShown: Boolean = true,
+
+        /** Whether to match APIs marked for removal instead of the normal API */
+        remove: Boolean = false,
+
+        /** Comparator to sort methods with, or null to use natural (source) order */
+        methodComparator: Comparator<MethodItem>? = null,
+
+        /** Comparator to sort fields with, or null to use natural (source) order */
+        fieldComparator: Comparator<FieldItem>? = null
+    ) : this(
+        visitConstructorsAsMethods, nestInnerClasses,
+        true, methodComparator,
+        fieldComparator,
+        ApiPredicate(codebase, ignoreShown = ignoreShown, matchRemoved = remove),
+        ApiPredicate(codebase, ignoreShown = true, ignoreRemoved = remove)
+    )
+
+
+    // The API visitor lazily visits packages only when there's a match within at least one class;
+    // this property keeps track of whether we've already visited the current package
+    var visitingPackage = false
+
+    open fun include(cls: ClassItem): Boolean = cls.emit
+}
+
diff --git a/src/main/java/com/android/tools/metalava/model/visitors/ItemVisitor.kt b/src/main/java/com/android/tools/metalava/model/visitors/ItemVisitor.kt
new file mode 100644
index 0000000..b04170b
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/model/visitors/ItemVisitor.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava.model.visitors
+
+import com.android.tools.metalava.model.ClassItem
+import com.android.tools.metalava.model.CompilationUnit
+import com.android.tools.metalava.model.ConstructorItem
+import com.android.tools.metalava.model.FieldItem
+import com.android.tools.metalava.model.Item
+import com.android.tools.metalava.model.MethodItem
+import com.android.tools.metalava.model.PackageItem
+import com.android.tools.metalava.model.ParameterItem
+
+open class ItemVisitor(
+    /**
+     * Whether constructors should be visited as part of a [#visitMethod] call
+     * instead of just a [#visitConstructor] call. Helps simplify visitors that
+     * don't care to distinguish between the two cases. Defaults to true.
+     */
+    val visitConstructorsAsMethods: Boolean = true,
+    /**
+     * Whether inner classes should be visited "inside" a class; when this property
+     * is true, inner classes are visited before the [#afterVisitClass] method is
+     * called; when false, it's done afterwards. Defaults to false.
+     */
+    val nestInnerClasses: Boolean = false,
+    /**
+     * Whether to skip empty packages
+     */
+    val skipEmptyPackages: Boolean = false
+) {
+
+    open fun skip(item: Item): Boolean = false
+
+    /** Visits the item. This is always called before other more specialized visit methods, such as [visitClass]. */
+    open fun visitItem(item: Item) {}
+
+    open fun visitCompilationUnit(unit: CompilationUnit) {}
+    open fun visitPackage(pkg: PackageItem) {}
+    open fun visitClass(cls: ClassItem) {}
+    open fun visitConstructor(constructor: ConstructorItem) {
+        if (visitConstructorsAsMethods) {
+            visitMethod(constructor)
+        }
+    }
+
+    open fun visitField(field: FieldItem) {}
+    open fun visitMethod(method: MethodItem) {}
+    open fun visitParameter(parameter: ParameterItem) {}
+
+    open fun afterVisitItem(item: Item) {}
+    open fun afterVisitPackage(pkg: PackageItem) {}
+    open fun afterVisitCompilationUnit(unit: CompilationUnit) {}
+    open fun afterVisitClass(cls: ClassItem) {}
+    open fun afterVisitConstructor(constructor: ConstructorItem) {
+        if (visitConstructorsAsMethods) {
+            afterVisitMethod(constructor)
+        }
+    }
+
+    open fun afterVisitField(field: FieldItem) {}
+    open fun afterVisitMethod(method: MethodItem) {}
+    open fun afterVisitParameter(parameter: ParameterItem) {}
+}
diff --git a/src/main/java/com/android/tools/metalava/model/visitors/PredicateVisitor.kt b/src/main/java/com/android/tools/metalava/model/visitors/PredicateVisitor.kt
new file mode 100644
index 0000000..45ae3fa
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/model/visitors/PredicateVisitor.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava.model.visitors
+
+import com.android.tools.metalava.model.Item
+
+open class PredicateVisitor(
+    private val predicate: (item: Item) -> Boolean,
+
+    /**
+     * Whether constructors should be visited as part of a [#visitMethod] call
+     * instead of just a [#visitConstructor] call. Helps simplify visitors that
+     * don't care to distinguish between the two cases. Defaults to true.
+     */
+    visitConstructorsAsMethods: Boolean = true,
+    /**
+     * Whether inner classes should be visited "inside" a class; when this property
+     * is true, inner classes are visited before the [#afterVisitClass] method is
+     * called; when false, it's done afterwards. Defaults to false.
+     */
+    nestInnerClasses: Boolean = false
+) : ItemVisitor(visitConstructorsAsMethods, nestInnerClasses) {
+    override fun skip(item: Item): Boolean {
+        return !predicate(item)
+    }
+}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/model/visitors/TypeVisitor.kt b/src/main/java/com/android/tools/metalava/model/visitors/TypeVisitor.kt
new file mode 100644
index 0000000..510d2b6
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/model/visitors/TypeVisitor.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava.model.visitors
+
+import com.android.tools.metalava.model.Item
+import com.android.tools.metalava.model.TypeItem
+
+open class TypeVisitor(val includeInterfaces: Boolean = false) {
+    open fun skip(item: Item): Boolean = false
+    open fun visitType(type: TypeItem, owner: Item) {}
+    open fun afterVisitType(type: TypeItem, owner: Item) {}
+}
\ No newline at end of file
diff --git a/src/main/java/com/android/tools/metalava/model/visitors/VisibleItemVisitor.kt b/src/main/java/com/android/tools/metalava/model/visitors/VisibleItemVisitor.kt
new file mode 100644
index 0000000..923e34f
--- /dev/null
+++ b/src/main/java/com/android/tools/metalava/model/visitors/VisibleItemVisitor.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava.model.visitors
+
+import com.android.tools.metalava.model.Item
+
+// TODO: Use ApiVisitor instead!
+open class VisibleItemVisitor(
+    /**
+     * Whether constructors should be visited as part of a [#visitMethod] call
+     * instead of just a [#visitConstructor] call. Helps simplify visitors that
+     * don't care to distinguish between the two cases. Defaults to true.
+     */
+    visitConstructorsAsMethods: Boolean = true,
+    /**
+     * Whether inner classes should be visited "inside" a class; when this property
+     * is true, inner classes are visited before the [#afterVisitClass] method is
+     * called; when false, it's done afterwards. Defaults to false.
+     */
+    nestInnerClasses: Boolean = false
+) : ItemVisitor(visitConstructorsAsMethods, nestInnerClasses) {
+    override fun skip(item: Item): Boolean {
+        return !item.included
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/metalava/AnnotationStatisticsTest.kt b/src/test/java/com/android/tools/metalava/AnnotationStatisticsTest.kt
new file mode 100644
index 0000000..c9ba501
--- /dev/null
+++ b/src/test/java/com/android/tools/metalava/AnnotationStatisticsTest.kt
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava
+
+import com.android.tools.lint.checks.infrastructure.TestFiles.base64gzip
+import com.android.tools.lint.checks.infrastructure.TestFiles.jar
+import org.junit.Test
+
+class AnnotationStatisticsTest : DriverTest() {
+    @Test
+
+    fun `Test emitting annotation statistics`() {
+        check(
+            extraArguments = arrayOf("--annotation-coverage-stats"),
+            expectedOutput = """
+                Nullness Annotation Coverage Statistics:
+                4 out of 6 methods were annotated (66%)
+                0 out of 0 fields were annotated (0%)
+                4 out of 5 parameters were annotated (80%)
+                """,
+            compatibilityMode = false,
+            signatureSource = """
+                package test.pkg {
+                  public class MyTest {
+                    ctor public MyTest();
+                    method public java.lang.Double convert0(java.lang.Float);
+                    method @android.support.annotation.Nullable public java.lang.Double convert1(@android.support.annotation.NonNull java.lang.Float);
+                    method @android.support.annotation.Nullable public java.lang.Double convert2(@android.support.annotation.NonNull java.lang.Float);
+                    method @android.support.annotation.Nullable public java.lang.Double convert3(@android.support.annotation.NonNull java.lang.Float);
+                    method @android.support.annotation.Nullable public java.lang.Double convert4(@android.support.annotation.NonNull java.lang.Float);
+                  }
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Test counting annotation usages of missing APIs`() {
+        check(
+            coverageJars = arrayOf(
+                /*
+                    package test.pkg;
+
+                    public class ApiUsage {
+                        ApiUsage() {
+                            new ApiSurface(null).annotated1("Hello");
+                            new ApiSurface(null).missing1(null);
+                        }
+
+                        Number myField = new ApiSurface(null, 5).missingField1;
+
+                        public void usage() {
+                            ApiSurface apiSurface = new ApiSurface(null, 5);
+                            apiSurface.annotated1("Hello");
+                            apiSurface.missing1(null);
+                            apiSurface.missing2(null, 5);
+                            apiSurface.missing3(5);
+                            apiSurface.missing4(null);
+                            apiSurface.missing5("Hello");
+                        }
+                    }
+                 */
+                jar(
+                    "libs/api-usage.jar",
+                    base64gzip(
+                        "test/pkg/ApiUsage.class", "" +
+                                "H4sIAAAAAAAAAH1Ta28SQRQ9Q4Fd1qW0VPD9KNZKWe0WaH3VmBiTRpKNfqiW" +
+                                "+HGgIw4uu4RdTPzqPzJRSDTxB/ijjHcGWqqlZjNn5945c86du7O/fn//CaCO" +
+                                "xxZyuG7ghoUEbmYIVjNYREmFt0ysqfdtBesK7igoK9hQUDHgGLjLYPQ+7Unh" +
+                                "HzLkvS7/yF2fBx335bDXEoNdhvQTGcj4KcNCeeOAIfk8PBQMOU8GYsJ5zVu+" +
+                                "UJvDNvcP+ECqeJpMxu9lxLDixSKK3f6HjvusL99EvCNIOTVUEwaL9+X+cPCO" +
+                                "tyko/EWdpols7YfDQVvsSSWbPVLZVAXbyGOFTOZsVEv3bGxi2caSgjxcMn4h" +
+                                "fD+0sYWqjRrqNraxY+M+Hth4qHKPUGVYPlUzw9KsQa9aXdGOGYpl79/kbkN1" +
+                                "KtuTUSSDjm4u6RXmEBXP4kEQxjwWirQ+j3Q6xWBO1c8SbswotbOKPMGpK5nG" +
+                                "f522Z9MdrNI9y9ElZDSosYQJGvQdKHOeZl2k9NpWZQxW+YHEW2aOsfAVyW9I" +
+                                "6XCMdN4YwWweRWyETPOLVioQFkkBSNKTIcUUSkjDhUF5wJ5o4wIu6houHft+" +
+                                "Jr6qpHZs6TkTG0frO8wcwWo6hOd0ym7q9ewJ5xJM7WEhSydbJBf6y+iUaxRV" +
+                                "6IxVcivqCrXTtAoLZVzGFaqD4arWuvYH9nECI6kDAAA="
+                    )
+                )
+            ),
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+
+                    import android.support.annotation.NonNull;
+                    import android.support.annotation.Nullable;
+
+                    public class ApiSurface {
+                        ApiSurface(Object param) {
+                        }
+
+                        ApiSurface(@Nullable Object param, int param2) {
+                        }
+
+                        @Nullable public Object annotated1(@NonNull Object param1) { return null; }
+                        public int annotated2(@NonNull Object param1, int param2) { return 1; }
+
+                        @NonNull public String annotatedField1 = "";
+                        public int annotatedField2;
+                        public Number missingField1;
+                        public Number missingField2;
+
+                        public int missing1(Object param1) { return 0; }
+                        public int missing2(Object param1, int param2) { return 0; }
+                        public Object missing3(int param1) { return null; }
+                        @Nullable public Object missing4(Object param1) { return null; }
+                        public Object missing5(@NonNull Object param1) { return null; }
+
+                        public class InnerClass {
+                            @Nullable public Object annotated3(@NonNull Object param1) { return null; }
+                            public int annotated4(@NonNull Object param1, int param2) { return 1; }
+                            public int missing1(Object param1) { return 0; }
+                            public int missing2(Object param1, int param2) { return 0; }
+                        }
+                    }
+                    """
+                ),
+                supportNonNullSource,
+                supportNullableSource
+            ),
+            expectedOutput = """
+                6 methods and fields were missing nullness annotations out of 7 total API references.
+                API nullness coverage is 14%
+
+                |--------------------------------------------------------------|------------------|
+                | Qualified Class Name                                         |      Usage Count |
+                |--------------------------------------------------------------|-----------------:|
+                | test.pkg.ApiSurface                                          |                7 |
+                |--------------------------------------------------------------|------------------|
+
+                Top referenced un-annotated members:
+
+                |--------------------------------------------------------------|------------------|
+                | Member                                                       |      Usage Count |
+                |--------------------------------------------------------------|-----------------:|
+                | ApiSurface.missing1(Object)                                  |                2 |
+                | ApiSurface.missingField1                                     |                1 |
+                | ApiSurface.missing2(Object, int)                             |                1 |
+                | ApiSurface.missing3(int)                                     |                1 |
+                | ApiSurface.missing4(Object)                                  |                1 |
+                | ApiSurface.missing5(Object)                                  |                1 |
+                |--------------------------------------------------------------|------------------|
+                """,
+            compatibilityMode = false
+        )
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/metalava/AnnotationsMergerTest.kt b/src/test/java/com/android/tools/metalava/AnnotationsMergerTest.kt
new file mode 100644
index 0000000..129f41e
--- /dev/null
+++ b/src/test/java/com/android/tools/metalava/AnnotationsMergerTest.kt
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava
+
+import org.junit.Test
+
+class AnnotationsMergerTest : DriverTest() {
+
+    // TODO: Test what happens when we have conflicting data
+    //   - NULLABLE_SOURCE on one non null on the other
+    //   - annotation specified with different parameters (e.g @Size(4) vs @Size(6))
+
+    @Test
+    fun `Signature files contain annotations`() {
+        check(
+            compatibilityMode = false,
+            outputKotlinStyleNulls = false,
+            includeSystemApiAnnotations = false,
+            omitCommonPackages = false,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+
+                    import android.support.annotation.NonNull;
+                    import android.support.annotation.Nullable;
+                    import android.annotation.IntRange;
+                    import android.support.annotation.UiThread;
+
+                    @UiThread
+                    public class MyTest {
+                        public @Nullable Number myNumber;
+                        public @Nullable Double convert(@NonNull Float f) { return null; }
+                        public @IntRange(from=10,to=20) int clamp(int i) { return 10; }
+                    }"""
+                ),
+                uiThreadSource,
+                intRangeAnnotationSource,
+                supportNonNullSource,
+                supportNullableSource
+            ),
+            // Skip the annotations themselves from the output
+            extraArguments = arrayOf(
+                "--hide-package", "android.annotation",
+                "--hide-package", "android.support.annotation"
+            ),
+            api = """
+                package test.pkg {
+                  @android.support.annotation.UiThread public class MyTest {
+                    ctor public MyTest();
+                    method @android.support.annotation.IntRange(from=10, to=20) public int clamp(int);
+                    method @android.support.annotation.Nullable public java.lang.Double convert(@android.support.annotation.NonNull java.lang.Float);
+                    field @android.support.annotation.Nullable public java.lang.Number myNumber;
+                  }
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Merged class and method annotations with no arguments`() {
+        check(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+
+                    public class MyTest {
+                        public Number myNumber;
+                        public Double convert(Float f) { return null; }
+                        public int clamp(int i) { return 10; }
+                    }
+                    """
+                )
+            ),
+            compatibilityMode = false,
+            outputKotlinStyleNulls = false,
+            omitCommonPackages = false,
+            mergeAnnotations = """<?xml version="1.0" encoding="UTF-8"?>
+                <root>
+                  <item name="test.pkg.MyTest">
+                    <annotation name="android.support.annotation.UiThread" />
+                  </item>
+                  <item name="test.pkg.MyTest java.lang.Double convert(java.lang.Float)">
+                    <annotation name="android.support.annotation.Nullable" />
+                  </item>
+                  <item name="test.pkg.MyTest java.lang.Double convert(java.lang.Float) 0">
+                    <annotation name="android.support.annotation.NonNull" />
+                  </item>
+                  <item name="test.pkg.MyTest myNumber">
+                    <annotation name="android.support.annotation.Nullable" />
+                  </item>
+                  <item name="test.pkg.MyTest int clamp(int)">
+                    <annotation name="android.support.annotation.IntRange">
+                      <val name="from" val="10" />
+                      <val name="to" val="20" />
+                    </annotation>
+                  </item>
+                  </root>
+                """,
+            api = """
+                package test.pkg {
+                  @android.support.annotation.UiThread public class MyTest {
+                    ctor public MyTest();
+                    method @android.support.annotation.IntRange(from=10, to=20) public int clamp(int);
+                    method @android.support.annotation.Nullable public java.lang.Double convert(@android.support.annotation.NonNull java.lang.Float);
+                    field @android.support.annotation.Nullable public java.lang.Number myNumber;
+                  }
+                }
+                """
+        )
+    }
+}
diff --git a/src/test/java/com/android/tools/metalava/ApiFileTest.kt b/src/test/java/com/android/tools/metalava/ApiFileTest.kt
new file mode 100644
index 0000000..51a9027
--- /dev/null
+++ b/src/test/java/com/android/tools/metalava/ApiFileTest.kt
@@ -0,0 +1,2091 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+@file:Suppress("ALL")
+
+package com.android.tools.metalava
+
+import org.junit.Ignore
+import org.junit.Test
+
+class ApiFileTest : DriverTest() {
+/*
+   Conditions to test:
+   - test all the error scenarios found in the notStrippable case!
+   - split up test into many individual test cases
+   - try referencing a class from an annotation!
+   - test having a throws list where some exceptions are hidden but extend
+     public exceptions: do we map over to the referenced ones?
+
+   - test type reference from all the possible places -- in type signatures - interfaces,
+     extends, throws, type bounds, etc.
+   - method which overrides @hide method: should appear in subclass (test chain
+     of two nested too)
+   - BluetoothGattCharacteristic.java#describeContents: Was marked @hide,
+     but is unhidden because it extends a public interface method
+   - package javadoc (also make sure merging both!, e.g. try having @hide in each)
+   - StopWatchMap -- inner class with @hide marks allh top levels!
+   - Test field inlining: should I include fields from an interface, if that
+     inteface was implemented by the parent class (and therefore appears there too?)
+     What if the superclass is abstract?
+   - Exposing package private classes. Test that I only do this for package private
+     classes, NOT Those marked @hide (is that, having @hide on a used type, illegal?)
+   - Test error handling (invalid @hide combinations))
+   - Consider what happens if we promote a package private class (because it's
+     extended by a public class), and then we restore its public members; the
+     override logic there isn't quite right. We've duplicated the significant-override
+     code to not skip private members, but that could change semantics. This isn't
+     ideal; instead we should now mark this class as public, and re-run the analysis
+     again (with the new hidden state for this class).
+   - compilation unit sorting - top level classes out of order
+   - Massive classes such as android.R.java? Maybe do synthetic test.
+   - HttpResponseCache implemented a public OkHttp interface, but the sole implementation
+     method was marked @hide, so the method doesn't show up. Is that some other rule --
+     that we skip interfaces if their implementation methods are marked @hide?
+   - Test recursive package filtering.
+ */
+
+    @Test
+    fun `Basic class signature extraction`() {
+        // Basic class; also checks that default constructor is made explicit
+        check(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    public class Foo {
+                    }
+                    """
+                )
+            ),
+            api = """
+                    package test.pkg {
+                      public class Foo {
+                        ctor public Foo();
+                      }
+                    }
+                    """
+        )
+    }
+
+    @Test
+    fun `Parameter Names in Java`() {
+        // Java code which explicitly specifies parameter names
+        check(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    import android.support.annotation.ParameterName;
+
+                    public class Foo {
+                        public void foo(int javaParameter1, @ParameterName("publicParameterName") int javaParameter2) {
+                        }
+                    }
+                    """
+                ),
+                supportParameterName
+            ),
+            api = """
+                    package test.pkg {
+                      public class Foo {
+                        ctor public Foo();
+                        method public void foo(int, int publicParameterName);
+                      }
+                    }
+                 """,
+            extraArguments = arrayOf("--hide-package", "android.support.annotation"),
+            checkDoclava1 = false /* doesn't support parameter names */
+        )
+    }
+
+    @Test
+    fun `Basic Kotlin class`() {
+        check(
+            sourceFiles = *arrayOf(
+                kotlin(
+                    """
+                    package test.pkg
+                    class Kotlin(val property1: String = "Default Value", arg2: Int) : Parent() {
+                        override fun method() = "Hello World"
+                        fun otherMethod(ok: Boolean, times: Int) {
+                        }
+
+                        var property2: String? = null
+
+                        private var someField = 42
+                        @JvmField
+                        var someField2 = 42
+                    }
+
+                    open class Parent {
+                        open fun method(): String? = null
+                        open fun method2(value: Boolean, value: Boolean?): String? = null
+                        open fun method3(value: Int?, value2: Int): Int = null
+                    }
+                    """
+                )
+            ),
+            api = """
+                package test.pkg {
+                  public final class Kotlin extends test.pkg.Parent {
+                    ctor public Kotlin(java.lang.String property1, int arg2);
+                    method public final java.lang.String getProperty1();
+                    method public final java.lang.String getProperty2();
+                    method public final void otherMethod(boolean ok, int times);
+                    method public final void setProperty2(java.lang.String p);
+                    field public int someField2;
+                  }
+                  public class Parent {
+                    ctor public Parent();
+                    method public java.lang.String method();
+                    method public java.lang.String method2(boolean value, java.lang.Boolean value);
+                    method public int method3(java.lang.Integer value, int value2);
+                  }
+                }
+                """,
+            checkDoclava1 = false /* doesn't support Kotlin... */
+        )
+    }
+
+    @Ignore("Still broken: UAST is missing reified methods, and some missing symbol resolution")
+    @Test
+    fun `Kotlin Reified Methods`() {
+        check(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+
+                    public class Context {
+                        @SuppressWarnings("unchecked")
+                        public final <T> T getSystemService(Class<T> serviceClass) {
+                            return null;
+                        }
+                    }
+                    """
+                ),
+                kotlin(
+                    """
+                    package test.pkg
+
+                    inline fun <reified T> Context.systemService1() = getSystemService(T::class.java)
+                    inline fun Context.systemService2() = getSystemService(String::class.java)
+                    """
+                )
+            ),
+            api = """
+                package test.pkg {
+                  public class Context {
+                    ctor public Context();
+                    method public final <T> T getSystemService(java.lang.Class<T>);
+                  }
+                  public final class _java_Kt {
+                    ctor public _java_Kt();
+                    method public static final error.NonExistentClass systemService2(test.pkg.Context);
+                  }
+                }
+                """,
+            checkDoclava1 = false /* doesn't support Kotlin... */
+        )
+    }
+
+    @Test
+    fun `Propagate Platform types in Kotlin`() {
+        check(
+            compatibilityMode = false,
+            outputKotlinStyleNulls = true,
+            sourceFiles = *arrayOf(
+                kotlin(
+                    """
+                    // Nullable Pair in Kotlin
+                    package androidx.util
+
+                    class NullableKotlinPair<out F, out S>(val first: F?, val second: S?)
+                    """
+                ),
+                kotlin(
+                    """
+                    // Non-nullable Pair in Kotlin
+                    package androidx.util
+                    class NonNullableKotlinPair<out F: Any, out S: Any>(val first: F, val second: S)
+                    """
+                ),
+                java(
+                    """
+                    // Platform nullability Pair in Java
+                    package androidx.util;
+
+                    @SuppressWarnings("WeakerAccess")
+                    public class PlatformJavaPair<F, S> {
+                        public final F first;
+                        public final S second;
+
+                        public PlatformJavaPair(F first, S second) {
+                            this.first = first;
+                            this.second = second;
+                        }
+                    }
+                """
+                ),
+                java(
+                    """
+                    // Platform nullability Pair in Java
+                    package androidx.util;
+                    import android.support.annotation.NonNull;
+                    import android.support.annotation.Nullable;
+
+                    @SuppressWarnings("WeakerAccess")
+                    public class NullableJavaPair<F, S> {
+                        public final @Nullable F first;
+                        public final @Nullable S second;
+
+                        public NullableJavaPair(@Nullable F first, @Nullable S second) {
+                            this.first = first;
+                            this.second = second;
+                        }
+                    }
+                    """
+                ),
+                java(
+                    """
+                    // Platform nullability Pair in Java
+                    package androidx.util;
+
+                    import android.support.annotation.NonNull;
+
+                    @SuppressWarnings("WeakerAccess")
+                    public class NonNullableJavaPair<F, S> {
+                        public final @NonNull F first;
+                        public final @NonNull S second;
+
+                        public NonNullableJavaPair(@NonNull F first, @NonNull S second) {
+                            this.first = first;
+                            this.second = second;
+                        }
+                    }
+                    """
+                ),
+                kotlin(
+                    """
+                    package androidx.util
+
+                    @Suppress("HasPlatformType") // Intentionally propagating platform type with unknown nullability.
+                    inline operator fun <F, S> PlatformJavaPair<F, S>.component1() = first
+                    """
+                ),
+                supportNonNullSource,
+                supportNullableSource
+            ),
+            api = """
+                package androidx.util {
+                  public class NonNullableJavaPair<F, S> {
+                    ctor public NonNullableJavaPair(F, S);
+                    field public final F first;
+                    field public final S second;
+                  }
+                  public final class NonNullableKotlinPair<F, S> {
+                    ctor public NonNullableKotlinPair(F first, S second);
+                    method public final F getFirst();
+                    method public final S getSecond();
+                  }
+                  public class NullableJavaPair<F, S> {
+                    ctor public NullableJavaPair(F?, S?);
+                    field public final F? first;
+                    field public final S? second;
+                  }
+                  public final class NullableKotlinPair<F, S> {
+                    ctor public NullableKotlinPair(F? first, S? second);
+                    method public final F? getFirst();
+                    method public final S? getSecond();
+                  }
+                  public class PlatformJavaPair<F, S> {
+                    ctor public PlatformJavaPair(F!, S!);
+                    field public final F! first;
+                    field public final S! second;
+                  }
+                  public final class TestKt {
+                    ctor public TestKt();
+                    method public static final <F, S> F! component1(androidx.util.PlatformJavaPair<F,S>);
+                  }
+                }
+                """,
+            extraArguments = arrayOf("--hide-package", "android.support.annotation"),
+            checkDoclava1 = false /* doesn't support Kotlin... */
+        )
+    }
+
+    @Test
+    fun `Extract class with generics`() {
+        // Basic interface with generics; makes sure <T extends Object> is written as just <T>
+        // Also include some more complex generics expressions to make sure they're serialized
+        // correctly (in particular, using fully qualified names instead of what appears in
+        // the source code.)
+        check(
+            checkDoclava1 = true,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    @SuppressWarnings("ALL")
+                    public interface MyInterface<T extends Object>
+                            extends MyBaseInterface {
+                    }
+                    """
+                ), java(
+                    """
+                    package a.b.c;
+                    @SuppressWarnings("ALL")
+                    public interface MyStream<T, S extends MyStream<T, S>> extends test.pkg.AutoCloseable {
+                    }
+                    """
+                ), java(
+                    """
+                    package test.pkg;
+                    @SuppressWarnings("ALL")
+                    public interface MyInterface2<T extends Number>
+                            extends MyBaseInterface {
+                        class TtsSpan<C extends MyInterface<?>> { }
+                        abstract class Range<T extends Comparable<? super T>> {
+                            protected String myString;
+                        }
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg;
+                    public interface MyBaseInterface {
+                        void fun(int a, String b);
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg;
+                    public interface MyOtherInterface extends MyBaseInterface, AutoCloseable {
+                        void fun(int a, String b);
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg;
+                    public interface AutoCloseable {
+                    }
+                    """
+                )
+            ),
+            api = """
+                    package a.b.c {
+                      public abstract interface MyStream<T, S extends a.b.c.MyStream<T, S>> implements test.pkg.AutoCloseable {
+                      }
+                    }
+                    package test.pkg {
+                      public abstract interface AutoCloseable {
+                      }
+                      public abstract interface MyBaseInterface {
+                        method public abstract void fun(int, java.lang.String);
+                      }
+                      public abstract interface MyInterface<T> implements test.pkg.MyBaseInterface {
+                      }
+                      public abstract interface MyInterface2<T extends java.lang.Number> implements test.pkg.MyBaseInterface {
+                      }
+                      public static abstract class MyInterface2.Range<T extends java.lang.Comparable<? super T>> {
+                        ctor public MyInterface2.Range();
+                        field protected java.lang.String myString;
+                      }
+                      public static class MyInterface2.TtsSpan<C extends test.pkg.MyInterface<?>> {
+                        ctor public MyInterface2.TtsSpan();
+                      }
+                      public abstract interface MyOtherInterface implements test.pkg.AutoCloseable test.pkg.MyBaseInterface {
+                      }
+                    }
+                """
+        )
+    }
+
+    @Test
+    fun `Basic class without default constructor, has constructors with args`() {
+        // Class without private constructors (shouldn't insert default constructor)
+        check(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    public class Foo {
+                        public Foo(int i) {
+
+                        }
+                        public Foo(int i, int j) {
+                        }
+                    }
+                    """
+                )
+            ),
+            api = """
+                package test.pkg {
+                  public class Foo {
+                    ctor public Foo(int);
+                    ctor public Foo(int, int);
+                  }
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Basic class without default constructor, has private constructor`() {
+        // Class without private constructors; no default constructor should be inserted
+        check(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    @SuppressWarnings("ALL")
+                    public class Foo {
+                        private Foo() {
+                        }
+                    }
+                    """
+                )
+            ),
+            api = """
+                package test.pkg {
+                  public class Foo {
+                  }
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Interface class extraction`() {
+        // Interface: makes sure the right modifiers etc are shown (and that "package private" methods
+        // in the interface are taken to be public etc)
+        check(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    @SuppressWarnings("ALL")
+                    public interface Foo {
+                        void foo();
+                    }
+                    """
+                )
+            ),
+            api = """
+                package test.pkg {
+                  public abstract interface Foo {
+                    method public abstract void foo();
+                  }
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Enum class extraction`() {
+        // Interface: makes sure the right modifiers etc are shown (and that "package private" methods
+        // in the interface are taken to be public etc)
+        check(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    @SuppressWarnings("ALL")
+                    public enum Foo {
+                        A, B;
+                    }
+                    """
+                )
+            ),
+            api = """
+                package test.pkg {
+                  public final class Foo extends java.lang.Enum {
+                    method public static test.pkg.Foo valueOf(java.lang.String);
+                    method public static final test.pkg.Foo[] values();
+                    enum_constant public static final test.pkg.Foo A;
+                    enum_constant public static final test.pkg.Foo B;
+                  }
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Enum class, non-compat mode`() {
+        @Suppress("ConstantConditionIf")
+        if (SKIP_NON_COMPAT) {
+            println("Skipping test for non-compatibility mode which isn't fully done yet")
+            return
+        }
+
+        // Interface: makes sure the right modifiers etc are shown (and that "package private" methods
+        // in the interface are taken to be public etc)
+        check(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    @SuppressWarnings("ALL")
+                    public enum Foo {
+                        A, B;
+                    }
+                    """
+                )
+            ),
+            compatibilityMode = false,
+            api = """
+                package test.pkg {
+                  public enum Foo {
+                    enum_constant public static final test.pkg.Foo! A;
+                    enum_constant public static final test.pkg.Foo! B;
+                  }
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Annotation class extraction`() {
+        // Interface: makes sure the right modifiers etc are shown (and that "package private" methods
+        // in the interface are taken to be public etc)
+        check(
+            // For unknown reasons, doclava1 behaves differently here than when invoked on the
+            // whole platform
+            checkDoclava1 = false,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    @SuppressWarnings("ALL")
+                    public @interface Foo {
+                        String value();
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package android.annotation;
+                    import static java.lang.annotation.ElementType.*;
+                    import java.lang.annotation.*;
+                    @Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
+                    @Retention(RetentionPolicy.CLASS)
+                    @SuppressWarnings("ALL")
+                    public @interface SuppressLint {
+                        String[] value();
+                    }
+                """
+                )
+            ),
+            api = """
+                package android.annotation {
+                  public abstract class SuppressLint implements java.lang.annotation.Annotation {
+                  }
+                }
+                package test.pkg {
+                  public abstract class Foo implements java.lang.annotation.Annotation {
+                  }
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Annotation class extraction, non-compat mode`() {
+        @Suppress("ConstantConditionIf")
+        if (SKIP_NON_COMPAT) {
+            println("Skipping test for non-compatibility mode which isn't fully done yet")
+            return
+        }
+
+        // Interface: makes sure the right modifiers etc are shown (and that "package private" methods
+        // in the interface are taken to be public etc)
+        check(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    public @interface Foo {
+                        String value();
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package android.annotation;
+                    import static java.lang.annotation.ElementType.*;
+                    import java.lang.annotation.*;
+                    @Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
+                    @Retention(RetentionPolicy.CLASS)
+                    @SuppressWarnings("ALL")
+                    public @interface SuppressLint {
+                        String[] value();
+                    }
+                    """
+                )
+            ),
+            compatibilityMode = false,
+            api = """
+                package android.annotation {
+                  public @interface SuppressLint {
+                    method public abstract String[]! value();
+                  }
+                }
+                package test.pkg {
+                  public @interface Foo {
+                    method public abstract String! value();
+                  }
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Superclass signature extraction`() {
+        // Make sure superclass statement is correct; inherited method from parent that has same
+        // signature isn't included in the child
+        check(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    @SuppressWarnings("ALL")
+                    public class Foo extends Super {
+                        @Override public void base() { }
+                        public void child() { }
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg;
+                    @SuppressWarnings("ALL")
+                    public class Super {
+                        public void base() { }
+                    }
+                    """
+                )
+            ),
+            api = """
+                package test.pkg {
+                  public class Foo extends test.pkg.Super {
+                    ctor public Foo();
+                    method public void child();
+                  }
+                  public class Super {
+                    ctor public Super();
+                    method public void base();
+                  }
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Extract fields with types and initial values`() {
+        check(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    @SuppressWarnings("ALL")
+                    public class Foo {
+                        private int hidden = 1;
+                        int hidden2 = 2;
+                        /** @hide */
+                        int hidden3 = 3;
+
+                        protected int field00; // No value
+                        public static final boolean field01 = true;
+                        public static final int field02 = 42;
+                        public static final long field03 = 42L;
+                        public static final short field04 = 5;
+                        public static final byte field05 = 5;
+                        public static final char field06 = 'c';
+                        public static final float field07 = 98.5f;
+                        public static final double field08 = 98.5;
+                        public static final String field09 = "String with \"escapes\" and \u00a9...";
+                        public static final double field10 = Double.NaN;
+                        public static final double field11 = Double.POSITIVE_INFINITY;
+
+                        public static final String GOOD_IRI_CHAR = "a-zA-Z0-9\u00a0-\ud7ff\uf900-\ufdcf\ufdf0-\uffef";
+                        public static final char HEX_INPUT = 61184;
+                    }
+                    """
+                )
+            ),
+            api = """
+                package test.pkg {
+                  public class Foo {
+                    ctor public Foo();
+                    field public static final java.lang.String GOOD_IRI_CHAR = "a-zA-Z0-9\u00a0-\ud7ff\uf900-\ufdcf\ufdf0-\uffef";
+                    field public static final char HEX_INPUT = 61184; // 0xef00 '\uef00'
+                    field protected int field00;
+                    field public static final boolean field01 = true;
+                    field public static final int field02 = 42; // 0x2a
+                    field public static final long field03 = 42L; // 0x2aL
+                    field public static final short field04 = 5; // 0x5
+                    field public static final byte field05 = 5; // 0x5
+                    field public static final char field06 = 99; // 0x0063 'c'
+                    field public static final float field07 = 98.5f;
+                    field public static final double field08 = 98.5;
+                    field public static final java.lang.String field09 = "String with \"escapes\" and \u00a9...";
+                    field public static final double field10 = (0.0/0.0);
+                    field public static final double field11 = (1.0/0.0);
+                  }
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Check all modifiers`() {
+        // Include as many modifiers as possible to see which ones are included
+        // in the signature files, and the expected sorting order.
+        // Note that the signature files treat "deprecated" as a fake modifier.
+        // Note also how the "protected" modifier on the interface method gets
+        // promoted to public.
+        check(
+            checkDoclava1 = true,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+
+                    @SuppressWarnings("ALL")
+                    public abstract class Foo {
+                        @Deprecated private static final long field1 = 5;
+                        @Deprecated private static volatile long field2 = 5;
+                        @Deprecated public static strictfp final synchronized void method1() { }
+                        @Deprecated public static final synchronized native void method2();
+                        @Deprecated protected static final class Inner1 { }
+                        @Deprecated protected static abstract  class Inner2 { }
+                        @Deprecated protected interface Inner3 {
+                            default void method3() { }
+                            static void method4(final int arg) { }
+                        }
+                    }
+                    """
+                )
+            ),
+
+            warnings = """
+                        src/test/pkg/Foo.java:4: warning: Class test.pkg.Foo.Inner3: @Deprecated annotation (present) and @deprecated doc tag (not present) do not match [DeprecationMismatch:113]
+                        src/test/pkg/Foo.java:5: warning: Class test.pkg.Foo.Inner2: @Deprecated annotation (present) and @deprecated doc tag (not present) do not match [DeprecationMismatch:113]
+                        src/test/pkg/Foo.java:6: warning: Class test.pkg.Foo.Inner1: @Deprecated annotation (present) and @deprecated doc tag (not present) do not match [DeprecationMismatch:113]
+                        src/test/pkg/Foo.java:7: warning: Method test.pkg.Foo.method2(): @Deprecated annotation (present) and @deprecated doc tag (not present) do not match [DeprecationMismatch:113]
+                        src/test/pkg/Foo.java:8: warning: Method test.pkg.Foo.method1(): @Deprecated annotation (present) and @deprecated doc tag (not present) do not match [DeprecationMismatch:113]
+                        """,
+
+            api = """
+                    package test.pkg {
+                      public abstract class Foo {
+                        ctor public Foo();
+                        method public static final deprecated synchronized void method1();
+                        method public static final deprecated synchronized void method2();
+                      }
+                      protected static final deprecated class Foo.Inner1 {
+                        ctor protected Foo.Inner1();
+                      }
+                      protected static abstract deprecated class Foo.Inner2 {
+                        ctor protected Foo.Inner2();
+                      }
+                      protected static abstract deprecated interface Foo.Inner3 {
+                        method public default void method3();
+                        method public static void method4(int);
+                      }
+                    }
+                """
+        )
+    }
+
+    @Test
+    fun `Check all modifiers, non-compat mode`() {
+        @Suppress("ConstantConditionIf")
+        if (SKIP_NON_COMPAT) {
+            @Suppress("ConstantConditionIf")
+            println("Skipping test for non-compatibility mode which isn't fully done yet")
+            return
+        }
+
+        // Like testModifiers but turns off compat mode, such that we have
+        // a modifier order more in line with standard code conventions
+        check(
+            compatibilityMode = false,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+
+                    @SuppressWarnings("ALL")
+                    public abstract class Foo {
+                        @Deprecated private static final long field1 = 5;
+                        @Deprecated private static volatile long field2 = 5;
+                        /** @deprecated */ @Deprecated public static strictfp final synchronized void method1() { }
+                        /** @deprecated */ @Deprecated public static final synchronized native void method2();
+                        /** @deprecated */ @Deprecated protected static final class Inner1 { }
+                        /** @deprecated */ @Deprecated protected static abstract class Inner2 { }
+                        /** @deprecated */ @Deprecated protected interface Inner3 {
+                            protected default void method3() { }
+                            static void method4(final int arg) { }
+                        }
+                    }
+                    """
+                )
+            ),
+            api = """
+                package test.pkg {
+                  public abstract class Foo {
+                    ctor public Foo();
+                    method deprecated public static final synchronized strictfp void method1();
+                    method deprecated public static final synchronized native void method2();
+                  }
+                  deprecated protected static final class Foo.Inner1 {
+                    ctor protected Foo.Inner1();
+                  }
+                  deprecated protected abstract static class Foo.Inner2 {
+                    ctor protected Foo.Inner2();
+                  }
+                  deprecated protected static interface Foo.Inner3 {
+                    method public default void method3();
+                    method public static void method4(int);
+                  }
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Package with only hidden classes should be removed from signature files`() {
+        // Checks that if we have packages that are hidden, or contain only hidden or doconly
+        // classes, the entire package is omitted from the signature file. Note how the test.pkg1.sub
+        // package is not marked @hide, but doclava now treats subpackages of a hidden package
+        // as also hidden.
+        check(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    ${"/** @hide hidden package */" /* avoid dangling javadoc warning */}
+                    package test.pkg1;
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg1;
+                    @SuppressWarnings("ALL")
+                    public class Foo {
+                        // Hidden by package hide
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg2;
+                    /** @hide hidden class in this package */
+                    @SuppressWarnings("ALL")
+                    public class Bar {
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg2;
+                    /** @doconly hidden class in this package */
+                    @SuppressWarnings("ALL")
+                    public class Baz {
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg1.sub;
+                    // Hidden by @hide in package above
+                    @SuppressWarnings("ALL")
+                    public class Test {
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg3;
+                    // The only really visible class
+                    @SuppressWarnings("ALL")
+                    public class Boo {
+                    }
+                    """
+                )
+            ),
+            api = """
+                package test.pkg3 {
+                  public class Boo {
+                    ctor public Boo();
+                  }
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Enums can be abstract`() {
+        // As per https://bugs.openjdk.java.net/browse/JDK-6287639
+        // abstract methods in enums should not be listed as abstract,
+        // but doclava1 does, so replicate this.
+        // Also checks that we handle both enum fields and regular fields
+        // and that they are listed separately.
+
+        check(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+
+                    @SuppressWarnings("ALL")
+                    public enum FooBar {
+                        ABC {
+                            @Override
+                            protected void foo() { }
+                        }, DEF {
+                            @Override
+                            protected void foo() { }
+                        };
+
+                        protected abstract void foo();
+                        public static int field1 = 1;
+                        public int field2 = 2;
+                    }
+                    """
+                )
+            ),
+            api = """
+                package test.pkg {
+                  public class FooBar extends java.lang.Enum {
+                    method protected abstract void foo();
+                    method public static test.pkg.FooBar valueOf(java.lang.String);
+                    method public static final test.pkg.FooBar[] values();
+                    enum_constant public static final test.pkg.FooBar ABC;
+                    enum_constant public static final test.pkg.FooBar DEF;
+                    field public static int field1;
+                    field public int field2;
+                  }
+                }
+            """
+        )
+    }
+
+    @Test
+    fun `Check erasure in throws-list`() {
+        // Makes sure that when we have a generic signature in the throws list we take
+        // the erasure instead (in compat mode); "Throwable" instead of "X" in the below
+        // test. Real world example: Optional.orElseThrow.
+        check(
+            compatibilityMode = true,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+
+                    import java.util.function.Supplier;
+
+                    @SuppressWarnings("ALL")
+                    public final class Test<T> {
+                        public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
+                            return null;
+                        }
+                    }
+                    """
+                )
+            ),
+            api = """
+                package test.pkg {
+                  public final class Test<T> {
+                    ctor public Test();
+                    method public <X extends java.lang.Throwable> T orElseThrow(java.util.function.Supplier<? extends X>) throws java.lang.Throwable;
+                  }
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Check various generics signature subtleties`() {
+        // Some additional declarations where PSI default type handling diffs from doclava1
+        check(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+
+                    @SuppressWarnings("ALL")
+                    public abstract class Collections {
+                        public static <T extends java.lang.Object & java.lang.Comparable<? super T>> T max(java.util.Collection<? extends T> collection) {
+                            return null;
+                        }
+                        public abstract <T extends java.util.Collection<java.lang.String>> T addAllTo(T t);
+                        public final class Range<T extends java.lang.Comparable<? super T>> { }
+                    }
+                    """
+                ), java(
+                    """
+                    package test.pkg;
+
+                    import java.util.Set;
+
+                    @SuppressWarnings("ALL")
+                    public class MoreAsserts {
+                        public static void assertEquals(String arg0, Set<? extends Object> arg1, Set<? extends Object> arg2) { }
+                        public static void assertEquals(Set<? extends Object> arg1, Set<? extends Object> arg2) { }
+                    }
+
+                    """
+                )
+            ),
+
+            // This is the output from doclava1; I'm not quite matching this yet (sorting order differs,
+            // and my heuristic to remove "extends java.lang.Object" is somehow preserved here. I'm
+            // not clear on when they do it and when they don't.
+            /*
+            api = """
+            package test.pkg {
+              public abstract class Collections {
+                ctor public Collections();
+                method public abstract <T extends java.util.Collection<java.lang.String>> T addAllTo(T);
+                method public static <T & java.lang.Comparable<? super T>> T max(java.util.Collection<? extends T>);
+              }
+              public final class Collections.Range<T extends java.lang.Comparable<? super T>> {
+                ctor public Collections.Range();
+              }
+              public class MoreAsserts {
+                ctor public MoreAsserts();
+                method public static void assertEquals(java.util.Set<? extends java.lang.Object>, java.util.Set<? extends java.lang.Object>);
+                method public static void assertEquals(java.lang.String, java.util.Set<? extends java.lang.Object>, java.util.Set<? extends java.lang.Object>);
+              }
+            }
+            """,
+            */
+            api = """
+                package test.pkg {
+                  public abstract class Collections {
+                    ctor public Collections();
+                    method public abstract <T extends java.util.Collection<java.lang.String>> T addAllTo(T);
+                    method public static <T extends java.lang.Object & java.lang.Comparable<? super T>> T max(java.util.Collection<? extends T>);
+                  }
+                  public final class Collections.Range<T extends java.lang.Comparable<? super T>> {
+                    ctor public Collections.Range();
+                  }
+                  public class MoreAsserts {
+                    ctor public MoreAsserts();
+                    method public static void assertEquals(java.lang.String, java.util.Set<?>, java.util.Set<?>);
+                    method public static void assertEquals(java.util.Set<?>, java.util.Set<?>);
+                  }
+                }
+                """,
+
+            // Can't check doclava1 on this: its output doesn't match javac, e.g. for the above declaration
+            // of max, javap shows this signature:
+            //   public static <T extends java.lang.Comparable<? super T>> T max(java.util.Collection<? extends T>);
+            // which matches metalava's output:
+            //   method public static <T & java.lang.Comparable<? super T>> T max(java.util.Collection<? extends T>);
+            // and not doclava1:
+            //   method public static <T extends java.lang.Object & java.lang.Comparable<? super T>> T max(java.util.Collection<? extends T>);
+
+            checkDoclava1 = false
+        )
+    }
+
+    @Test
+    fun `Check instance methods in enums`() {
+        // Make sure that when we have instance methods in an enum they're handled
+        // correctly (there's some special casing around enums to insert extra methods
+        // that was broken, as exposed by ChronoUnit#toString)
+        check(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+
+                    @SuppressWarnings("ALL")
+                    public interface TempUnit {
+                        @Override
+                        String toString();
+                    }
+                     """
+                ),
+                java(
+                    """
+                    package test.pkg;
+
+                    @SuppressWarnings("ALL")
+                    public enum ChronUnit implements TempUnit {
+                        C, B, A;
+
+                        public String valueOf(int x) {
+                            return Integer.toString(x + 5);
+                        }
+
+                        public String values(String separator) {
+                            return null;
+                        }
+
+                        @Override
+                        public String toString() {
+                            return name();
+                        }
+                    }
+                """
+                )
+            ),
+            importedPackages = emptyList(),
+            api = """
+                package test.pkg {
+                  public final class ChronUnit extends java.lang.Enum implements test.pkg.TempUnit {
+                    method public static test.pkg.ChronUnit valueOf(java.lang.String);
+                    method public java.lang.String valueOf(int);
+                    method public static final test.pkg.ChronUnit[] values();
+                    method public final java.lang.String values(java.lang.String);
+                    enum_constant public static final test.pkg.ChronUnit A;
+                    enum_constant public static final test.pkg.ChronUnit B;
+                    enum_constant public static final test.pkg.ChronUnit C;
+                  }
+                  public abstract interface TempUnit {
+                    method public abstract java.lang.String toString();
+                  }
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Mixing enums and fields`() {
+        // Checks sorting order of enum constant values
+        val source = """
+            package java.nio.file.attribute {
+              public final class AclEntryPermission extends java.lang.Enum {
+                method public static java.nio.file.attribute.AclEntryPermission valueOf(java.lang.String);
+                method public static final java.nio.file.attribute.AclEntryPermission[] values();
+                enum_constant public static final java.nio.file.attribute.AclEntryPermission APPEND_DATA;
+                enum_constant public static final java.nio.file.attribute.AclEntryPermission DELETE;
+                enum_constant public static final java.nio.file.attribute.AclEntryPermission DELETE_CHILD;
+                enum_constant public static final java.nio.file.attribute.AclEntryPermission EXECUTE;
+                enum_constant public static final java.nio.file.attribute.AclEntryPermission READ_ACL;
+                enum_constant public static final java.nio.file.attribute.AclEntryPermission READ_ATTRIBUTES;
+                enum_constant public static final java.nio.file.attribute.AclEntryPermission READ_DATA;
+                enum_constant public static final java.nio.file.attribute.AclEntryPermission READ_NAMED_ATTRS;
+                enum_constant public static final java.nio.file.attribute.AclEntryPermission SYNCHRONIZE;
+                enum_constant public static final java.nio.file.attribute.AclEntryPermission WRITE_ACL;
+                enum_constant public static final java.nio.file.attribute.AclEntryPermission WRITE_ATTRIBUTES;
+                enum_constant public static final java.nio.file.attribute.AclEntryPermission WRITE_DATA;
+                enum_constant public static final java.nio.file.attribute.AclEntryPermission WRITE_NAMED_ATTRS;
+                enum_constant public static final java.nio.file.attribute.AclEntryPermission WRITE_OWNER;
+                field public static final java.nio.file.attribute.AclEntryPermission ADD_FILE;
+                field public static final java.nio.file.attribute.AclEntryPermission ADD_SUBDIRECTORY;
+                field public static final java.nio.file.attribute.AclEntryPermission LIST_DIRECTORY;
+              }
+            }
+                    """
+        check(
+            signatureSource = source,
+            api = source
+        )
+    }
+
+    @Test
+    fun `Superclass filtering, should skip intermediate hidden classes`() {
+        check(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    @SuppressWarnings("ALL")
+                    public class MyClass extends HiddenParent {
+                        public void method4() { }
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg;
+                    /** @hide */
+                    @SuppressWarnings("ALL")
+                    public class HiddenParent extends HiddenParent2 {
+                        public static final String CONSTANT = "MyConstant";
+                        protected int mContext;
+                        public void method3() { }
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg;
+                    /** @hide */
+                    @SuppressWarnings("ALL")
+                    public class HiddenParent2 extends PublicParent {
+                        public void method2() { }
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg;
+                    @SuppressWarnings("ALL")
+                    public class PublicParent {
+                        public void method1() { }
+                    }
+                    """
+                )
+            ),
+            // Notice how the intermediate methods (method2, method3) have been removed
+            includeStrippedSuperclassWarnings = true,
+            warnings = "src/test/pkg/MyClass.java:3: warning: Public class test.pkg.MyClass stripped of unavailable superclass test.pkg.HiddenParent [HiddenSuperclass:111]",
+            api = """
+                package test.pkg {
+                  public class MyClass extends test.pkg.PublicParent {
+                    ctor public MyClass();
+                    method public void method4();
+                  }
+                  public class PublicParent {
+                    ctor public PublicParent();
+                    method public void method1();
+                  }
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Inheriting from package private classes, package private class should be included`() {
+        check(
+            checkDoclava1 = true,
+            sourceFiles =
+            *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    @SuppressWarnings("ALL")
+                    public class MyClass extends HiddenParent {
+                        public void method1() { }
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg;
+                    @SuppressWarnings("ALL")
+                    class HiddenParent {
+                        public static final String CONSTANT = "MyConstant";
+                        protected int mContext;
+                        public void method2() { }
+                    }
+                    """
+                )
+            ),
+            warnings = "",
+            api = """
+                    package test.pkg {
+                      public class MyClass {
+                        ctor public MyClass();
+                        method public void method1();
+                      }
+                    }
+            """
+        )
+    }
+
+    @Test
+    fun `When implementing rather than extending package private class, inline members instead`() {
+        // If you implement a package private interface, we just remove it and inline the members into
+        // the subclass
+        check(
+            compatibilityMode = true,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    public class MyClass implements HiddenInterface {
+                        @Override public void method() { }
+                        @Override public void other() { }
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg;
+                    public interface OtherInterface {
+                        void other();
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg;
+                    interface HiddenInterface extends OtherInterface {
+                        void method() { }
+                        String CONSTANT = "MyConstant";
+                    }
+                    """
+                )
+            ),
+            api = """
+                package test.pkg {
+                  public class MyClass implements test.pkg.OtherInterface {
+                    ctor public MyClass();
+                    method public void method();
+                    method public void other();
+                    field public static final java.lang.String CONSTANT = "MyConstant";
+                  }
+                  public abstract interface OtherInterface {
+                    method public abstract void other();
+                  }
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Implementing package private class, non-compat mode`() {
+        @Suppress("ConstantConditionIf")
+        if (SKIP_NON_COMPAT) {
+            println("Skipping test for non-compatibility mode which isn't fully done yet")
+            return
+        }
+
+        // Like the previous test, but in non compat mode we correctly
+        // include all the non-hidden public interfaces into the signature
+
+        // BUG: Note that we need to implement the parent
+        check(
+            compatibilityMode = false,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    public class MyClass implements HiddenInterface {
+                        @Override public void method() { }
+                        @Override public void other() { }
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg;
+                    public interface OtherInterface {
+                        void other();
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg;
+                    interface HiddenInterface extends OtherInterface {
+                        void method() { }
+                        String CONSTANT = "MyConstant";
+                    }
+                    """
+                )
+            ),
+            api = """
+                package test.pkg {
+                  public class MyClass implements test.pkg.OtherInterface {
+                    ctor public MyClass();
+                    method public void method();
+                    method public void other();
+                    field public static final String! CONSTANT = "MyConstant";
+                  }
+                  public interface OtherInterface {
+                    method public void other();
+                  }
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Default modifiers should be omitted`() {
+        // If signatures vary only by the "default" modifier in the interface, don't show it on the implementing
+        // class
+        check(
+            sourceFiles =
+            *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+
+                    public class MyClass implements SuperInterface {
+                        @Override public void method() {  }
+                        @Override public void method2() { }
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg;
+
+                    public interface SuperInterface {
+                        void method();
+                        default void method2() {
+                        }
+                    }
+                    """
+                )
+            ),
+            api = """
+                package test.pkg {
+                  public class MyClass implements test.pkg.SuperInterface {
+                    ctor public MyClass();
+                    method public void method();
+                  }
+                  public abstract interface SuperInterface {
+                    method public abstract void method();
+                    method public default void method2();
+                  }
+                }
+            """
+        )
+    }
+
+    @Test
+    fun `Override via different throws list should be included`() {
+        // If a method overrides another but changes the throws list, the overriding
+        // method must be listed in the subclass. This is observed for example in
+        // AbstractCursor#finalize, which omits the throws clause from Object's finalize.
+        check(
+            sourceFiles =
+            *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+
+                    public abstract class AbstractCursor extends Parent {
+                        @Override protected void finalize2() {  } // note: not throws Throwable!
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg;
+
+                    @SuppressWarnings("RedundantThrows")
+                    public class Parent {
+                        protected void finalize2() throws Throwable {
+                        }
+                    }
+                    """
+                )
+            ),
+            api = """
+                package test.pkg {
+                  public abstract class AbstractCursor extends test.pkg.Parent {
+                    ctor public AbstractCursor();
+                    method protected void finalize2();
+                  }
+                  public class Parent {
+                    ctor public Parent();
+                    method protected void finalize2() throws java.lang.Throwable;
+                  }
+                }
+            """
+        )
+    }
+
+    @Test
+    fun `Implementing interface method`() {
+        // If you have a public method that implements an interface method,
+        // they'll vary in the "abstract" modifier, but it shouldn't be listed on the
+        // class. This is an issue for example for the ZonedDateTime#getLong method
+        // implementing the TemporalAccessor#getLong method
+        check(
+            sourceFiles = *arrayOf(
+//                java(
+//                    """
+//                    package test.pkg;
+//                    public interface SomeInterface {
+//                        long getLong();
+//                    }
+//                    """
+//                ),
+                java(
+                    """
+                    package test.pkg;
+                    public interface SomeInterface2 {
+                        @Override default long getLong() {
+                            return 42;
+                        }
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg;
+                    public class Foo implements /*SomeInterface,*/ SomeInterface2 {
+                        @Override
+                        public long getLong() { return 0L; }
+                    }
+                    """
+                )
+            ),
+//            api = """
+//                package test.pkg {
+//                  public class Foo implements test.pkg.SomeInterface test.pkg.SomeInterface2 {
+//                    ctor public Foo();
+//                  }
+//                  public abstract interface SomeInterface {
+//                    method public abstract long getLong();
+//                  }
+//                  public abstract interface SomeInterface2 {
+//                    method public default long getLong();
+//                  }
+//                }
+//                """
+            api = """
+            package test.pkg {
+              public class Foo implements test.pkg.SomeInterface2 {
+                ctor public Foo();
+              }
+              public abstract interface SomeInterface2 {
+                method public default long getLong();
+              }
+            }
+        """
+        )
+    }
+
+    @Test
+    fun `Check basic @remove scenarios`() {
+        // Test basic @remove handling for methods and fields
+        check(
+            checkDoclava1 = true,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    @SuppressWarnings("JavaDoc")
+                    public class Bar {
+                        /** @removed */
+                        public Bar() { }
+                        public int field;
+                        public void test() { }
+                        /** @removed */
+                        public int removedField;
+                        /** @removed */
+                        public void removedMethod() { }
+                        /** @removed and @hide - should not be listed */
+                        public int hiddenField;
+
+                        /** @removed */
+                        public class Inner { }
+
+                        public class Inner2 {
+                            public class Inner3 {
+                                /** @removed */
+                                public class Inner4 { }
+                            }
+                        }
+
+                        public class Inner5 {
+                            public class Inner6 {
+                                public class Inner7 {
+                                    /** @removed */
+                                    public int removed;
+                                }
+                            }
+                        }
+                    }
+                    """
+                )
+            ),
+            removedApi = """
+                package test.pkg {
+                  public class Bar {
+                    ctor public Bar();
+                    method public void removedMethod();
+                    field public int removedField;
+                  }
+                  public class Bar.Inner {
+                    ctor public Bar.Inner();
+                  }
+                  public class Bar.Inner2.Inner3.Inner4 {
+                    ctor public Bar.Inner2.Inner3.Inner4();
+                  }
+                  public class Bar.Inner5.Inner6.Inner7 {
+                    field public int removed;
+                  }
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Check @remove class`() {
+        // Test removing classes
+        check(
+            checkDoclava1 = true,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    /** @removed */
+                    @SuppressWarnings("JavaDoc")
+                    public class Foo {
+                        public void foo() { }
+                        public class Inner {
+                        }
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg;
+                    @SuppressWarnings("JavaDoc")
+                    public class Bar implements Parcelable {
+                        public int field;
+                        public void method();
+
+                        /** @removed */
+                        public int removedField;
+                        /** @removed */
+                        public void removedMethod() { }
+
+                        public class Inner1 {
+                        }
+                        /** @removed */
+                        public class Inner2 {
+                        }
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg;
+                    @SuppressWarnings("ALL")
+                    public interface Parcelable {
+                        void method();
+                    }
+                    """
+                )
+            ),
+            /*
+            I expected this: but doclava1 doesn't do that (and we now match its behavior)
+            package test.pkg {
+              public class Bar {
+                method public void removedMethod();
+                field public int removedField;
+              }
+              public class Bar.Inner2 {
+              }
+              public class Foo {
+                method public void foo();
+              }
+            }
+             */
+            removedApi = """
+                    package test.pkg {
+                      public class Bar implements test.pkg.Parcelable {
+                        method public void removedMethod();
+                        field public int removedField;
+                      }
+                      public class Bar.Inner2 {
+                        ctor public Bar.Inner2();
+                      }
+                      public class Foo {
+                        ctor public Foo();
+                        method public void foo();
+                      }
+                      public class Foo.Inner {
+                        ctor public Foo.Inner();
+                      }
+                    }
+                """
+        )
+    }
+
+    @Test
+    fun `Test include overridden @Deprecated even if annotated with @hide`() {
+        check(
+            checkDoclava1 = true,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    @SuppressWarnings("JavaDoc")
+                    public class Child extends Parent {
+                        /**
+                        * @deprecated
+                        * @hide
+                        */
+                        @Deprecated @Override
+                        public String toString() {
+                            return "Child";
+                        }
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg;
+                    public class Parent {
+                        public String toString() {
+                            return "Parent";
+                        }
+                    }
+                    """
+                )
+            ),
+            api = """
+                    package test.pkg {
+                      public class Child extends test.pkg.Parent {
+                        ctor public Child();
+                        method public deprecated java.lang.String toString();
+                      }
+                      public class Parent {
+                        ctor public Parent();
+                      }
+                    }
+                    """
+        )
+    }
+
+    @Test
+    fun `Indirect Field Includes from Interfaces`() {
+        // Real-world example: include ZipConstants into ZipFile and JarFile
+        check(
+            checkDoclava1 = true,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg1;
+                    interface MyConstants {
+                        long CONSTANT1 = 12345;
+                        long CONSTANT2 = 67890;
+                        long CONSTANT3 = 42;
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg1;
+                    import java.io.Closeable;
+                    @SuppressWarnings("WeakerAccess")
+                    public class MyParent implements MyConstants, Closeable {
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg2;
+
+                    import test.pkg1.MyParent;
+                    public class MyChild extends MyParent {
+                    }
+                    """
+                )
+
+            ),
+            api = """
+                    package test.pkg1 {
+                      public class MyParent implements java.io.Closeable {
+                        ctor public MyParent();
+                        field public static final long CONSTANT1 = 12345L; // 0x3039L
+                        field public static final long CONSTANT2 = 67890L; // 0x10932L
+                        field public static final long CONSTANT3 = 42L; // 0x2aL
+                      }
+                    }
+                    package test.pkg2 {
+                      public class MyChild extends test.pkg1.MyParent {
+                        ctor public MyChild();
+                        field public static final long CONSTANT1 = 12345L; // 0x3039L
+                        field public static final long CONSTANT2 = 67890L; // 0x10932L
+                        field public static final long CONSTANT3 = 42L; // 0x2aL
+                      }
+                    }
+                """
+        )
+    }
+
+    @Test
+    fun `Skip interfaces from packages explicitly hidden via arguments`() {
+        // Real-world example: HttpResponseCache implements OkCacheContainer but hides the only inherited method
+        check(
+            checkDoclava1 = true,
+            extraArguments = arrayOf(
+                "--hide-package", "com.squareup.okhttp"
+            ),
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package android.net.http;
+                    import com.squareup.okhttp.Cache;
+                    import com.squareup.okhttp.OkCacheContainer;
+                    import java.io.Closeable;
+                    import java.net.ResponseCache;
+                    @SuppressWarnings("JavaDoc")
+                    public final class HttpResponseCache implements Closeable, OkCacheContainer {
+                        /** @hide Needed for OkHttp integration. */
+                        @Override
+                        public Cache getCache() {
+                            return delegate.getCache();
+                        }
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package com.squareup.okhttp;
+                    public interface OkCacheContainer {
+                      Cache getCache();
+                    }
+                    """
+                )
+            ),
+            api = """
+                package android.net.http {
+                  public final class HttpResponseCache implements java.io.Closeable {
+                    ctor public HttpResponseCache();
+                  }
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Private API signatures`() {
+        check(
+            checkDoclava1 = false, // doclava1 doesn't have the same behavior: see
+            // https://android-review.googlesource.com/c/platform/external/doclava/+/589515
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                        package test.pkg;
+                        public class Class1 {
+                            Class1(int arg) { }
+                            /** @hide */
+                            public void method1() { }
+                            void method2() { }
+                            private void method3() { }
+                            public int field1 = 1;
+                            protected int field2 = 2;
+                            int field3 = 3;
+                            float[][] field4 = 3;
+                            long[] field5 = null;
+                            private int field6 = 4;
+                            void myVarargsMethod(int x, String... args) { }
+
+                            public class Inner { // Fully public, should not be included
+                                 public void publicMethod() { }
+                            }
+                        }
+                    """
+                ),
+
+                java(
+                    """
+                        package test.pkg;
+                        class Class2 {
+                            public void method4() { }
+
+                            private class Class3 {
+                                public void method5() { }
+                            }
+                        }
+                    """
+                ),
+
+                java(
+                    """
+                        package test.pkg;
+                        /** @doconly */
+                        class Class4 {
+                            public void method5() { }
+                        }
+                    """
+                )
+            ),
+            privateApi = """
+                package test.pkg {
+                  public class Class1 {
+                    ctor Class1(int);
+                    method public void method1();
+                    method void method2();
+                    method private void method3();
+                    method void myVarargsMethod(int, java.lang.String...);
+                    field int field3;
+                    field float[][] field4;
+                    field long[] field5;
+                    field private int field6;
+                  }
+                   class Class2 {
+                    ctor Class2();
+                    method public void method4();
+                  }
+                  private class Class2.Class3 {
+                    ctor private Class2.Class3();
+                    method public void method5();
+                  }
+                   class Class4 {
+                    ctor Class4();
+                    method public void method5();
+                  }
+                }
+                """,
+            privateDexApi = """
+                Ltest/pkg/Class1;-><init>(I)V
+                Ltest/pkg/Class1;->method1()V
+                Ltest/pkg/Class1;->method2()V
+                Ltest/pkg/Class1;->method3()V
+                Ltest/pkg/Class1;->myVarargsMethod(I[Ljava/lang/String;)V
+                Ltest/pkg/Class1;->field3:I
+                Ltest/pkg/Class1;->field4:[[F
+                Ltest/pkg/Class1;->field5:[J
+                Ltest/pkg/Class1;->field6:I
+                Ltest/pkg/Class2;
+                Ltest/pkg/Class2;-><init>()V
+                Ltest/pkg/Class2;->method4()V
+                Ltest/pkg/Class2${"$"}Class3;
+                Ltest/pkg/Class2${"$"}Class3;-><init>()V
+                Ltest/pkg/Class2${"$"}Class3;->method5()V
+                Ltest/pkg/Class4;
+                Ltest/pkg/Class4;-><init>()V
+                Ltest/pkg/Class4;->method5()V
+                """
+        )
+    }
+
+    @Test
+    fun `Extend from multiple interfaces`() {
+        // Real-world example: XmlResourceParser
+        check(
+            checkDoclava1 = true,
+            checkCompilation = true,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package android.content.res;
+                    import android.util.AttributeSet;
+                    import org.xmlpull.v1.XmlPullParser;
+                    import my.AutoCloseable;
+
+                    @SuppressWarnings("UnnecessaryInterfaceModifier")
+                    public interface XmlResourceParser extends XmlPullParser, AttributeSet, AutoCloseable {
+                        public void close();
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package android.util;
+                    @SuppressWarnings("WeakerAccess")
+                    public interface AttributeSet {
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package my;
+                    public interface AutoCloseable {
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package org.xmlpull.v1;
+                    @SuppressWarnings("WeakerAccess")
+                    public interface XmlPullParser {
+                    }
+                    """
+                )
+            ),
+            api = """
+                package android.content.res {
+                  public abstract interface XmlResourceParser implements android.util.AttributeSet my.AutoCloseable org.xmlpull.v1.XmlPullParser {
+                    method public abstract void close();
+                  }
+                }
+                package android.util {
+                  public abstract interface AttributeSet {
+                  }
+                }
+                package my {
+                  public abstract interface AutoCloseable {
+                  }
+                }
+                package org.xmlpull.v1 {
+                  public abstract interface XmlPullParser {
+                  }
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Including private interfaces from types`() {
+        check(
+            checkDoclava1 = true,
+            sourceFiles = *arrayOf(
+                java("""package test.pkg1; interface Interface1 { }"""),
+                java("""package test.pkg1; abstract class Class1 { }"""),
+                java("""package test.pkg1; abstract class Class2 { }"""),
+                java("""package test.pkg1; abstract class Class3 { }"""),
+                java("""package test.pkg1; abstract class Class4 { }"""),
+                java("""package test.pkg1; abstract class Class5 { }"""),
+                java("""package test.pkg1; abstract class Class6 { }"""),
+                java("""package test.pkg1; abstract class Class7 { }"""),
+                java("""package test.pkg1; abstract class Class8 { }"""),
+                java("""package test.pkg1; abstract class Class9 { }"""),
+                java(
+                    """
+                    package test.pkg1;
+
+                    import java.util.List;
+                    import java.util.Map;
+                    public abstract class Usage implements List<Class1> {
+                       <T extends java.lang.Comparable<? super T>> void sort(java.util.List<T> list) {}
+                       public Class3 myClass1 = null;
+                       public List<? extends Class4> myClass2 = null;
+                       public Map<String, ? extends Class5> myClass3 = null;
+                       public <T extends Class6> void mySort(List<Class7> list, T element) {}
+                       public void ellipsisType(Class8... myargs);
+                       public void arrayType(Class9[] myargs);
+                    }
+                    """
+                )
+            ),
+
+            // TODO: Test annotations! (values, annotation classes, etc.)
+            warnings = """
+                    src/test/pkg1/Usage.java:1: warning: Parameter myargs references hidden type class test.pkg1.Class9. [HiddenTypeParameter:121]
+                    src/test/pkg1/Usage.java:2: warning: Parameter myargs references hidden type class test.pkg1.Class8. [HiddenTypeParameter:121]
+                    src/test/pkg1/Usage.java:3: warning: Parameter list references hidden type class test.pkg1.Class7. [HiddenTypeParameter:121]
+                    src/test/pkg1/Usage.java:4: warning: Field Usage.myClass3 references hidden type class test.pkg1.Class5. [HiddenTypeParameter:121]
+                    src/test/pkg1/Usage.java:5: warning: Field Usage.myClass2 references hidden type class test.pkg1.Class4. [HiddenTypeParameter:121]
+                    src/test/pkg1/Usage.java:6: warning: Field Usage.myClass1 references hidden type test.pkg1.Class3. [HiddenTypeParameter:121]
+                    """,
+            api = """
+                    package test.pkg1 {
+                      public abstract class Usage implements java.util.List {
+                        ctor public Usage();
+                        method public void arrayType(test.pkg1.Class9[]);
+                        method public void ellipsisType(test.pkg1.Class8...);
+                        method public <T extends test.pkg1.Class6> void mySort(java.util.List<test.pkg1.Class7>, T);
+                        field public test.pkg1.Class3 myClass1;
+                        field public java.util.List<? extends test.pkg1.Class4> myClass2;
+                        field public java.util.Map<java.lang.String, ? extends test.pkg1.Class5> myClass3;
+                      }
+                    }
+                """
+        )
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/metalava/ApiFromTextTest.kt b/src/test/java/com/android/tools/metalava/ApiFromTextTest.kt
new file mode 100644
index 0000000..880376e
--- /dev/null
+++ b/src/test/java/com/android/tools/metalava/ApiFromTextTest.kt
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava
+
+import org.intellij.lang.annotations.Language
+import org.junit.Test
+
+class ApiFromTextTest : DriverTest() {
+
+    @Test
+    fun `Loading a signature file and writing the API back out`() {
+        val source = """
+                package test.pkg {
+                  public class MyTest {
+                    ctor public MyTest();
+                    method public int clamp(int);
+                    method public java.lang.Double convert(java.lang.Float);
+                    field public static final java.lang.String ANY_CURSOR_ITEM_TYPE = "vnd.android.cursor.item/*";
+                    field public java.lang.Number myNumber;
+                  }
+                }
+                """
+
+        check(
+            compatibilityMode = true,
+            signatureSource = source,
+            api = source
+        )
+    }
+
+    @Test
+    fun `Test generics, superclasses and interfaces`() {
+        val source = """
+            package a.b.c {
+              public abstract interface MyStream<T, S extends a.b.c.MyStream<T, S>> {
+              }
+            }
+            package test.pkg {
+              public final class Foo extends java.lang.Enum {
+                ctor public Foo(int);
+                ctor public Foo(int, int);
+                method public static test.pkg.Foo valueOf(java.lang.String);
+                method public static final test.pkg.Foo[] values();
+                enum_constant public static final test.pkg.Foo A;
+                enum_constant public static final test.pkg.Foo B;
+              }
+              public abstract interface MyBaseInterface {
+              }
+              public abstract interface MyInterface<T> implements test.pkg.MyBaseInterface {
+              }
+              public abstract interface MyInterface2<T extends java.lang.Number> implements test.pkg.MyBaseInterface {
+              }
+              public static abstract class MyInterface2.Range<T extends java.lang.Comparable<? super T>> {
+                ctor public MyInterface2.Range();
+              }
+              public static class MyInterface2.TtsSpan<C extends test.pkg.MyInterface<?>> {
+                ctor public MyInterface2.TtsSpan();
+              }
+              public final class Test<T> {
+                ctor public Test();
+                method public abstract <T extends java.util.Collection<java.lang.String>> T addAllTo(T);
+                method public static <T & java.lang.Comparable<? super T>> T max(java.util.Collection<? extends T>);
+                method public <X extends java.lang.Throwable> T orElseThrow(java.util.function.Supplier<? extends X>) throws java.lang.Throwable;
+                field public static java.util.List<java.lang.String> LIST;
+              }
+            }
+                """
+
+        check(
+            compatibilityMode = true,
+            signatureSource = source,
+            api = source
+        )
+    }
+
+    @Test
+    fun `Test constants`() {
+        val source = """
+                package test.pkg {
+                  public class Foo2 {
+                    ctor public Foo2();
+                    field public static final java.lang.String GOOD_IRI_CHAR = "a-zA-Z0-9\u00a0-\ud7ff\uf900-\ufdcf\ufdf0-\uffef";
+                    field public static final char HEX_INPUT = 61184; // 0xef00 '\uef00'
+                    field protected int field00;
+                    field public static final boolean field01 = true;
+                    field public static final int field02 = 42; // 0x2a
+                    field public static final long field03 = 42L; // 0x2aL
+                    field public static final short field04 = 5; // 0x5
+                    field public static final byte field05 = 5; // 0x5
+                    field public static final char field06 = 99; // 0x0063 'c'
+                    field public static final float field07 = 98.5f;
+                    field public static final double field08 = 98.5;
+                    field public static final java.lang.String field09 = "String with \"escapes\" and \u00a9...";
+                    field public static final double field10 = (0.0/0.0);
+                    field public static final double field11 = (1.0/0.0);
+                  }
+                }
+                """
+
+        check(
+            compatibilityMode = true,
+            signatureSource = source,
+            api = source
+        )
+    }
+
+    @Test
+    fun `Test inner classes`() {
+        val source = """
+                package test.pkg {
+                  public abstract class Foo {
+                    ctor public Foo();
+                    method public static final deprecated synchronized void method1();
+                    method public static final deprecated synchronized void method2();
+                  }
+                  protected static final deprecated class Foo.Inner1 {
+                    ctor protected Foo.Inner1();
+                  }
+                  protected static abstract deprecated class Foo.Inner2 {
+                    ctor protected Foo.Inner2();
+                  }
+                  protected static abstract deprecated interface Foo.Inner3 {
+                    method public default void method3();
+                    method public static void method4(int);
+                  }
+                }
+                """
+
+        check(
+            compatibilityMode = true,
+            signatureSource = source,
+            api = source
+        )
+    }
+
+    @Test
+    fun `Test throws`() {
+        val source = """
+                package test.pkg {
+                  public final class Test<T> {
+                    ctor public Test();
+                    method public <X extends java.lang.Throwable> T orElseThrow(java.util.function.Supplier<? extends X>) throws java.lang.Throwable;
+                  }
+                }
+                """
+
+        check(
+            compatibilityMode = true,
+            signatureSource = source,
+            api = source
+        )
+    }
+
+    @Test
+    fun `Loading a signature file with annotations on classes, fields, methods and parameters`() {
+        @Language("TEXT")
+        val source = """
+                package test.pkg {
+                  @android.support.annotation.UiThread public class MyTest {
+                    ctor public MyTest();
+                    method @android.support.annotation.IntRange(from=10, to=20) public int clamp(int);
+                    method public java.lang.Double? convert(java.lang.Float myPublicName);
+                    field public java.lang.Number? myNumber;
+                  }
+                }
+                """
+
+        check(
+            compatibilityMode = false,
+            inputKotlinStyleNulls = true,
+            omitCommonPackages = false,
+            signatureSource = source,
+            api = source
+        )
+    }
+
+    @Test
+    fun `Enums and annotations`() {
+        // In non-compat mode we write interfaces out as "@interface" (instead of abstract class)
+        // and similarly for enums we write "enum" instead of "class extends java.lang.Enum".
+        // Make sure we can also read this back in.
+        val source = """
+                package android.annotation {
+                  public @interface SuppressLint {
+                    method public abstract String[] value();
+                  }
+                }
+                package test.pkg {
+                  public enum Foo {
+                    enum_constant public static final test.pkg.Foo A;
+                    enum_constant public static final test.pkg.Foo B;
+                  }
+                }
+                """
+
+        check(
+            compatibilityMode = false,
+            outputKotlinStyleNulls = false,
+            signatureSource = source,
+            api = source
+        )
+    }
+
+    @Test
+    fun `Enums and annotations exported to compat`() {
+        val source = """
+                package android.annotation {
+                  public @interface SuppressLint {
+                  }
+                }
+                package test.pkg {
+                  public final enum Foo {
+                    enum_constant public static final test.pkg.Foo A;
+                    enum_constant public static final test.pkg.Foo B;
+                  }
+                }
+                """
+
+        check(
+            compatibilityMode = true,
+            signatureSource = source,
+            api = """
+                package android.annotation {
+                  public abstract class SuppressLint implements java.lang.annotation.Annotation {
+                  }
+                }
+                package test.pkg {
+                  public final class Foo extends java.lang.Enum {
+                    enum_constant public static final test.pkg.Foo A;
+                    enum_constant public static final test.pkg.Foo B;
+                  }
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Sort throws list by full name`() {
+        check(
+            compatibilityMode = true,
+            signatureSource = """
+                    package android.accounts {
+                      public abstract interface AccountManagerFuture<V> {
+                        method public abstract boolean cancel(boolean);
+                        method public abstract V getResult() throws android.accounts.OperationCanceledException, java.io.IOException, android.accounts.AuthenticatorException;
+                        method public abstract V getResult(long, java.util.concurrent.TimeUnit) throws android.accounts.OperationCanceledException, java.io.IOException, android.accounts.AuthenticatorException;
+                        method public abstract boolean isCancelled();
+                        method public abstract boolean isDone();
+                      }
+                    }
+                    """,
+            api = """
+                    package android.accounts {
+                      public abstract interface AccountManagerFuture<V> {
+                        method public abstract boolean cancel(boolean);
+                        method public abstract V getResult() throws android.accounts.AuthenticatorException, java.io.IOException, android.accounts.OperationCanceledException;
+                        method public abstract V getResult(long, java.util.concurrent.TimeUnit) throws android.accounts.AuthenticatorException, java.io.IOException, android.accounts.OperationCanceledException;
+                        method public abstract boolean isCancelled();
+                        method public abstract boolean isDone();
+                      }
+                    }
+                    """
+        )
+    }
+
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/metalava/CompatibilityCheckTest.kt b/src/test/java/com/android/tools/metalava/CompatibilityCheckTest.kt
new file mode 100644
index 0000000..fc95b52
--- /dev/null
+++ b/src/test/java/com/android/tools/metalava/CompatibilityCheckTest.kt
@@ -0,0 +1,377 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava
+
+import org.junit.Test
+
+class
+CompatibilityCheckTest : DriverTest() {
+    @Test
+    fun `Change between class and interface`() {
+        check(
+            checkCompatibility = true,
+            warnings = """
+                TESTROOT/load-api.txt:2: error: Class test.pkg.MyTest1 changed class/interface declaration [ChangedClass:23]
+                TESTROOT/load-api.txt:4: error: Class test.pkg.MyTest2 changed class/interface declaration [ChangedClass:23]
+                """,
+            compatibilityMode = false,
+            previousApi = """
+                package test.pkg {
+                  public class MyTest1 {
+                  }
+                  public interface MyTest2 {
+                  }
+                  public class MyTest3 {
+                  }
+                  public interface MyTest4 {
+                  }
+                }
+                """,
+            // MyTest1 and MyTest2 reversed from class to interface or vice versa, MyTest3 and MyTest4 unchanged
+            signatureSource = """
+                package test.pkg {
+                  public interface MyTest1 {
+                  }
+                  public class MyTest2 {
+                  }
+                  public class MyTest3 {
+                  }
+                  public interface MyTest4 {
+                  }
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Interfaces should not be dropped`() {
+        check(
+            checkCompatibility = true,
+            warnings = """
+                TESTROOT/load-api.txt:2: error: Class test.pkg.MyTest1 changed class/interface declaration [ChangedClass:23]
+                TESTROOT/load-api.txt:4: error: Class test.pkg.MyTest2 changed class/interface declaration [ChangedClass:23]
+                """,
+            compatibilityMode = false,
+            previousApi = """
+                package test.pkg {
+                  public class MyTest1 {
+                  }
+                  public interface MyTest2 {
+                  }
+                  public class MyTest3 {
+                  }
+                  public interface MyTest4 {
+                  }
+                }
+                """,
+            // MyTest1 and MyTest2 reversed from class to interface or vice versa, MyTest3 and MyTest4 unchanged
+            signatureSource = """
+                package test.pkg {
+                  public interface MyTest1 {
+                  }
+                  public class MyTest2 {
+                  }
+                  public class MyTest3 {
+                  }
+                  public interface MyTest4 {
+                  }
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Ensure warnings for removed APIs`() {
+        check(
+            checkCompatibility = true,
+            warnings = """
+                TESTROOT/previous-api.txt:3: error: Removed method test.pkg.MyTest1.method [RemovedMethod:9]
+                TESTROOT/previous-api.txt:4: error: Removed field test.pkg.MyTest1.field [RemovedField:10]
+                TESTROOT/previous-api.txt:6: error: Removed class test.pkg.MyTest2 [RemovedClass:8]
+                """,
+            compatibilityMode = false,
+            previousApi = """
+                package test.pkg {
+                  public class MyTest1 {
+                    method public Double method(Float);
+                    field public Double field;
+                  }
+                  public class MyTest2 {
+                    method public Double method(Float);
+                    field public Double field;
+                  }
+                }
+                package test.pkg.other {
+                }
+                """,
+            signatureSource = """
+                package test.pkg {
+                  public class MyTest1 {
+                  }
+                }
+                """,
+            api = """
+                package test.pkg {
+                  public class MyTest1 {
+                  }
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Flag invalid nullness changes`() {
+        check(
+            checkCompatibility = true,
+            warnings = """
+                TESTROOT/load-api.txt:5: error: Attempted to remove @Nullable annotation from method test.pkg.MyTest.convert3 [InvalidNullConversion:40]
+                TESTROOT/load-api.txt:5: error: Attempted to remove @Nullable annotation from parameter arg1 in test.pkg.MyTest.convert3 [InvalidNullConversion:40]
+                TESTROOT/load-api.txt:6: error: Attempted to remove @NonNull annotation from method test.pkg.MyTest.convert4 [InvalidNullConversion:40]
+                TESTROOT/load-api.txt:6: error: Attempted to remove @NonNull annotation from parameter arg1 in test.pkg.MyTest.convert4 [InvalidNullConversion:40]
+                TESTROOT/load-api.txt:7: error: Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter arg1 in test.pkg.MyTest.convert5 [InvalidNullConversion:40]
+                TESTROOT/load-api.txt:8: error: Attempted to change method return from @NonNull to @Nullable: incompatible change for method test.pkg.MyTest.convert6 [InvalidNullConversion:40]
+                """,
+            compatibilityMode = false,
+            outputKotlinStyleNulls = false,
+            previousApi = """
+                package test.pkg {
+                  public class MyTest {
+                    method public Double convert1(Float);
+                    method public Double convert2(Float);
+                    method @Nullable public Double convert3(@Nullable Float);
+                    method @NonNull public Double convert4(@NonNull Float);
+                    method @Nullable public Double convert5(@Nullable Float);
+                    method @NonNull public Double convert6(@NonNull Float);
+                  }
+                }
+                """,
+            // Changes: +nullness, -nullness, nullable->nonnull, nonnull->nullable
+            signatureSource = """
+                package test.pkg {
+                  public class MyTest {
+                    method @Nullable public Double convert1(@Nullable Float);
+                    method @NonNull public Double convert2(@NonNull Float);
+                    method public Double convert3(Float);
+                    method public Double convert4(Float);
+                    method @NonNull public Double convert5(@NonNull Float);
+                    method @Nullable public Double convert6(@Nullable Float);
+                  }
+                }
+                """,
+            api = """
+                package test.pkg {
+                  public class MyTest {
+                    method @Nullable public Double convert1(@Nullable Float);
+                    method @NonNull public Double convert2(@NonNull Float);
+                    method public Double convert3(Float);
+                    method public Double convert4(Float);
+                    method @NonNull public Double convert5(@NonNull Float);
+                    method @Nullable public Double convert6(@Nullable Float);
+                  }
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Kotlin Nullness`() {
+        check(
+            checkCompatibility = true,
+            warnings = """
+                src/test/pkg/Outer.kt:2: error: Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter string in test.pkg.Outer.Inner.method3 [InvalidNullConversion:40]
+                src/test/pkg/Outer.kt:3: error: Attempted to change method return from @NonNull to @Nullable: incompatible change for method test.pkg.Outer.Inner.method2 [InvalidNullConversion:40]
+                src/test/pkg/Outer.kt:3: error: Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter string in test.pkg.Outer.Inner.method2 [InvalidNullConversion:40]
+                src/test/pkg/Outer.kt:5: error: Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter string in test.pkg.Outer.method3 [InvalidNullConversion:40]
+                src/test/pkg/Outer.kt:6: error: Attempted to change method return from @NonNull to @Nullable: incompatible change for method test.pkg.Outer.method2 [InvalidNullConversion:40]
+                src/test/pkg/Outer.kt:6: error: Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter string in test.pkg.Outer.method2 [InvalidNullConversion:40]
+                """,
+            compatibilityMode = false,
+            inputKotlinStyleNulls = true,
+            outputKotlinStyleNulls = true,
+            previousApi = """
+                    package test.pkg {
+                      public final class Outer {
+                        ctor public Outer();
+                        method public final String? method1(String, String?);
+                        method public final String method2(String?, String);
+                        method public final String? method3(String, String?);
+                      }
+                      public static final class Outer.Inner {
+                        ctor public Outer.Inner();
+                        method public final String method2(String?, String);
+                        method public final String? method3(String, String?);
+                      }
+                    }
+                """,
+            sourceFiles = *arrayOf(
+                kotlin(
+                    """
+                    package test.pkg
+
+                    class Outer {
+                        fun method1(string: String, maybeString: String?): String? = null
+                        fun method2(string: String, maybeString: String?): String? = null
+                        fun method3(maybeString: String?, string : String): String = ""
+                        class Inner {
+                            fun method2(string: String, maybeString: String?): String? = null
+                            fun method3(maybeString: String?, string : String): String = ""
+                        }
+                    }
+                    """
+                )
+            ),
+            api = """
+                package test.pkg {
+                  public final class Outer {
+                    ctor public Outer();
+                    method public final String? method1(String string, String? maybeString);
+                    method public final String? method2(String string, String? maybeString);
+                    method public final String method3(String? maybeString, String string);
+                  }
+                  public static final class Outer.Inner {
+                    ctor public Outer.Inner();
+                    method public final String? method2(String string, String? maybeString);
+                    method public final String method3(String? maybeString, String string);
+                  }
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Java Parameter Name Change`() {
+        check(
+            checkCompatibility = true,
+            warnings = """
+                src/test/pkg/JavaClass.java:1: error: Attempted to change parameter name from secondParameter to newName in method test.pkg.JavaClass.method2 [ParameterNameChange:41]
+                src/test/pkg/JavaClass.java:2: error: Attempted to remove parameter name from parameter newName in test.pkg.JavaClass.method1 in method test.pkg.JavaClass.method1 [ParameterNameChange:41]
+                """,
+            compatibilityMode = false,
+            previousApi = """
+                package test.pkg {
+                  public class JavaClass {
+                    ctor public JavaClass();
+                    method public String method1(String parameterName);
+                    method public String method2(String firstParameter, String secondParameter);
+                  }
+                }
+                """,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    import android.support.annotation.ParameterName;
+
+                    public class JavaClass {
+                        public String method1(String newName) { return null; }
+                        public String method2(@ParameterName("firstParameter") String s, @ParameterName("newName") String prevName) { return null; }
+                    }
+                    """
+                ),
+                supportParameterName
+            ),
+            api = """
+                package test.pkg {
+                  public class JavaClass {
+                    ctor public JavaClass();
+                    method public String! method1(String!);
+                    method public String! method2(String! firstParameter, String! newName);
+                  }
+                }
+                """,
+            extraArguments = arrayOf("--hide-package", "android.support.annotation")
+        )
+    }
+
+    @Test
+    fun `Kotlin Parameter Name Change`() {
+        check(
+            checkCompatibility = true,
+            warnings = """
+                src/test/pkg/KotlinClass.kt:1: error: Attempted to change parameter name from prevName to newName in method test.pkg.KotlinClass.method1 [ParameterNameChange:41]
+                """,
+            compatibilityMode = false,
+            inputKotlinStyleNulls = true,
+            outputKotlinStyleNulls = true,
+            previousApi = """
+                package test.pkg {
+                  public final class KotlinClass {
+                    ctor public KotlinClass();
+                    method public final String? method1(String prevName);
+                  }
+                }
+                """,
+            sourceFiles = *arrayOf(
+                kotlin(
+                    """
+                    package test.pkg
+
+                    class KotlinClass {
+                        fun method1(newName: String): String? = null
+                    }
+                    """
+                )
+            ),
+            api = """
+                package test.pkg {
+                  public final class KotlinClass {
+                    ctor public KotlinClass();
+                    method public final String? method1(String newName);
+                  }
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Add flag new methods but not overrides from platform`() {
+        check(
+            checkCompatibility = true,
+            warnings = """
+                src/test/pkg/MyClass.java:2: error: Added field test.pkg.MyClass.newField [AddedField:5]
+                src/test/pkg/MyClass.java:3: error: Added method test.pkg.MyClass.method2 [AddedMethod:4]
+                """,
+            compatibilityMode = false,
+            previousApi = """
+                package test.pkg {
+                  public class MyClass {
+                    method public String method1(String);
+                  }
+                }
+                """,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+
+                    public class MyClass  {
+                        private MyClass() { }
+                        public String method1(String newName) { return null; }
+                        public String method2(String newName) { return null; }
+                        public int newField = 5;
+                        public String toString() { return "Hello World"; }
+                    }
+                    """
+                )
+            )
+        )
+    }
+
+    // TODO: Check method signatures changing incompatibly (look especially out for adding new overloaded
+    // methods and comparator getting confused!)
+    //   ..equals on the method items should actually be very useful!
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/metalava/DocAnalyzerTest.kt b/src/test/java/com/android/tools/metalava/DocAnalyzerTest.kt
new file mode 100644
index 0000000..49a3a02
--- /dev/null
+++ b/src/test/java/com/android/tools/metalava/DocAnalyzerTest.kt
@@ -0,0 +1,1030 @@
+package com.android.tools.metalava
+
+import com.android.tools.metalava.model.psi.trimDocIndent
+import org.junit.Assert.assertEquals
+import org.junit.Test
+
+/** Tests for the [DocAnalyzer] which enhances the docs */
+class DocAnalyzerTest : DriverTest() {
+    // TODO: Test @StringDef
+
+    @Test
+    fun `Basic documentation generation test`() {
+        check(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    import android.annotation.Nullable;
+                    import android.annotation.NonNull;
+                    public class Foo {
+                        /** These are the docs for method1. */
+                        @Nullable public Double method1(@NonNull Double factor1, @NonNull Double factor2) { }
+                        /** These are the docs for method2. It can sometimes return null. */
+                        @Nullable public Double method2(@NonNull Double factor1, @NonNull Double factor2) { }
+                        @Nullable public Double method3(@NonNull Double factor1, @NonNull Double factor2) { }
+                    }
+                    """
+                ),
+
+                nonNullSource,
+                nullableSource
+            ),
+            checkCompilation = false, // needs android.support annotations in classpath
+            checkDoclava1 = false,
+            stubs = arrayOf(
+                """
+                package test.pkg;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public class Foo {
+                public Foo() { throw new RuntimeException("Stub!"); }
+                /**
+                 * These are the docs for method1.
+                 * @param factor1 This value must never be {@code null}.
+                 * @param factor2 This value must never be {@code null}.
+                 * @return This value may be {@code null}.
+                 */
+                @android.support.annotation.Nullable public java.lang.Double method1(@android.support.annotation.NonNull java.lang.Double factor1, @android.support.annotation.NonNull java.lang.Double factor2) { throw new RuntimeException("Stub!"); }
+                /**
+                 * These are the docs for method2. It can sometimes return null.
+                 * @param factor1 This value must never be {@code null}.
+                 * @param factor2 This value must never be {@code null}.
+                 */
+                @android.support.annotation.Nullable public java.lang.Double method2(@android.support.annotation.NonNull java.lang.Double factor1, @android.support.annotation.NonNull java.lang.Double factor2) { throw new RuntimeException("Stub!"); }
+                /**
+                 * @param factor1 This value must never be {@code null}.
+                 * @param factor2 This value must never be {@code null}.
+                 * @return This value may be {@code null}.
+                 */
+                @android.support.annotation.Nullable public java.lang.Double method3(@android.support.annotation.NonNull java.lang.Double factor1, @android.support.annotation.NonNull java.lang.Double factor2) { throw new RuntimeException("Stub!"); }
+                }
+                """
+            )
+        )
+    }
+
+    @Test
+    fun `Fix first sentence handling`() {
+        check(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package android.annotation;
+
+                    import static java.lang.annotation.ElementType.*;
+                    import static java.lang.annotation.RetentionPolicy.CLASS;
+                    import java.lang.annotation.*;
+
+                    /**
+                     * Denotes that an integer parameter, field or method return value is expected
+                     * to be a String resource reference (e.g. {@code android.R.string.ok}).
+                     */
+                    @Documented
+                    @Retention(CLASS)
+                    @Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE})
+                    public @interface StringRes {
+                    }
+                    """
+                )
+            ),
+            checkCompilation = true,
+            checkDoclava1 = false,
+            stubs = arrayOf(
+                """
+                package android.annotation;
+                /**
+                 * Denotes that an integer parameter, field or method return value is expected
+                 * to be a String resource reference (e.g.&nbsp;{@code android.R.string.ok}).
+                 */
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public @interface StringRes {
+                }
+                """
+            )
+        )
+    }
+
+    @Test
+    fun `Fix typo replacement`() {
+        check(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    /** This is an API for Andriod */
+                    public class Foo {
+                    }
+                    """
+                )
+            ),
+            checkCompilation = true,
+            checkDoclava1 = false,
+            warnings = "src/test/pkg/Foo.java:2: lint: Replaced Andriod with Android in documentation for class test.pkg.Foo [Typo:131]",
+            stubs = arrayOf(
+                """
+                package test.pkg;
+                /** This is an API for Android */
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public class Foo {
+                public Foo() { throw new RuntimeException("Stub!"); }
+                }
+                """
+            )
+        )
+    }
+
+    @Test
+    fun `Document Permissions`() {
+        check(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+
+                    import android.Manifest;
+                    import android.annotation.RequiresPermission;
+
+                    public class PermissionTest {
+                        @RequiresPermission(Manifest.permission.ACCESS_COARSE_LOCATION)
+                        public void test1() {
+                        }
+
+                        @RequiresPermission(allOf = Manifest.permission.ACCESS_COARSE_LOCATION)
+                        public void test2() {
+                        }
+
+                        @RequiresPermission(anyOf = {Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION})
+                        public void test3() {
+                        }
+
+                        @RequiresPermission(allOf = {Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCOUNT_MANAGER})
+                        public void test4() {
+                        }
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package android;
+
+                    public abstract class Manifest {
+                        public static final class permission {
+                            public static final String ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION";
+                            public static final String ACCESS_FINE_LOCATION = "android.permission.ACCESS_FINE_LOCATION";
+                            public static final String ACCOUNT_MANAGER = "android.permission.ACCOUNT_MANAGER";
+                        }
+                    }
+                    """
+                ),
+                requiresPermissionSource
+            ),
+            checkCompilation = false, // needs android.support annotations in classpath
+            checkDoclava1 = false,
+            stubs = arrayOf(
+                """
+                package test.pkg;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public class PermissionTest {
+                public PermissionTest() { throw new RuntimeException("Stub!"); }
+                /**
+                 * Requires {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}
+                 */
+                @android.support.annotation.RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) public void test1() { throw new RuntimeException("Stub!"); }
+                /**
+                 * Requires {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}
+                 */
+                @android.support.annotation.RequiresPermission(allOf=android.Manifest.permission.ACCESS_COARSE_LOCATION) public void test2() { throw new RuntimeException("Stub!"); }
+                /**
+                 * Requires {@link android.Manifest.permission#ACCESS_COARSE_LOCATION} or {@link android.Manifest.permission#ACCESS_FINE_LOCATION}
+                 */
+                @android.support.annotation.RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public void test3() { throw new RuntimeException("Stub!"); }
+                /**
+                 * Requires {@link android.Manifest.permission#ACCESS_COARSE_LOCATION} and {@link android.Manifest.permission#ACCOUNT_MANAGER}
+                 */
+                @android.support.annotation.RequiresPermission(allOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCOUNT_MANAGER}) public void test4() { throw new RuntimeException("Stub!"); }
+                }
+                """
+            )
+        )
+    }
+
+    @Test
+    fun `Document ranges`() {
+        check(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+
+                    import android.Manifest;
+                    import android.annotation.IntRange;
+
+                    public class RangeTest {
+                        @IntRange(from = 10)
+                        public int test1(@IntRange(from = 20) int range2) { return 15; }
+
+                        @IntRange(from = 10, to = 20)
+                        public int test2() { return 15; }
+
+                        @IntRange(to = 100)
+                        public int test3() { return 50; }
+                    }
+                    """
+                ),
+                intRangeAnnotationSource
+            ),
+            checkCompilation = true,
+            checkDoclava1 = false,
+            stubs = arrayOf(
+                """
+                package test.pkg;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public class RangeTest {
+                public RangeTest() { throw new RuntimeException("Stub!"); }
+                /**
+                 * @param range2 Value is 20 or greater
+                 * @return Value is 10 or greater
+                 */
+                @android.support.annotation.IntRange(from=10) public int test1(@android.support.annotation.IntRange(from=20) int range2) { throw new RuntimeException("Stub!"); }
+                /**
+                 * @return Value is between 10 and 20 inclusive
+                 */
+                @android.support.annotation.IntRange(from=10, to=20) public int test2() { throw new RuntimeException("Stub!"); }
+                /**
+                 * @return Value is 100 or less
+                 */
+                @android.support.annotation.IntRange(to=100) public int test3() { throw new RuntimeException("Stub!"); }
+                }
+                """
+            )
+        )
+    }
+
+    @Test
+    fun `Merging in documentation snippets from annotation memberDoc and classDoc`() {
+        check(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    import android.support.annotation.UiThread;
+                    import android.support.annotation.WorkerThread;
+                    @UiThread
+                    public class RangeTest {
+                        @WorkerThread
+                        public int test1() { }
+                    }
+                    """
+                ),
+                uiThreadSource,
+                workerThreadSource
+            ),
+            checkCompilation = true,
+            checkDoclava1 = false,
+            stubs = arrayOf(
+                """
+                package test.pkg;
+                /** Methods in this class must be called on the thread that originally created
+                 *            this UI element, unless otherwise noted. This is typically the
+                 *            main thread of your app. * */
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                @android.support.annotation.UiThread public class RangeTest {
+                public RangeTest() { throw new RuntimeException("Stub!"); }
+                /** This method may take several seconds to complete, so it should
+                 *            only be called from a worker thread. */
+                @android.support.annotation.WorkerThread public int test1() { throw new RuntimeException("Stub!"); }
+                }
+                """
+            )
+        )
+    }
+
+    @Test
+    fun `Warn about multiple threading annotations`() {
+        check(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    import android.support.annotation.UiThread;
+                    import android.support.annotation.WorkerThread;
+                    public class RangeTest {
+                        @UiThread @WorkerThread
+                        public int test1() { }
+                    }
+                    """
+                ),
+                uiThreadSource,
+                workerThreadSource
+            ),
+            checkCompilation = true,
+            checkDoclava1 = false,
+            warnings = "src/test/pkg/RangeTest.java:2: warning: Found more than one threading annotation on method test.pkg.RangeTest.test1(); the auto-doc feature does not handle this correctly [MultipleThreadAnnotations:133]",
+            stubs = arrayOf(
+                """
+                package test.pkg;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public class RangeTest {
+                public RangeTest() { throw new RuntimeException("Stub!"); }
+                /** This method must be called on the thread that originally created
+                 *            this UI element. This is typically the main thread of your app.
+                 * This method may take several seconds to complete, so it should
+                 *  *            only be called from a worker thread.
+                 */
+                @android.support.annotation.UiThread @android.support.annotation.WorkerThread public int test1() { throw new RuntimeException("Stub!"); }
+                }
+                """
+            )
+        )
+    }
+
+    @Test
+    fun `Typedefs`() {
+        check(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+
+                    import android.annotation.IntDef;
+                    import android.annotation.IntRange;
+
+                    import java.lang.annotation.Retention;
+                    import java.lang.annotation.RetentionPolicy;
+
+                    @SuppressWarnings({"UnusedDeclaration", "WeakerAccess"})
+                    public class TypedefTest {
+                        @IntDef({STYLE_NORMAL, STYLE_NO_TITLE, STYLE_NO_FRAME, STYLE_NO_INPUT})
+                        @Retention(RetentionPolicy.SOURCE)
+                        private @interface DialogStyle {}
+
+                        public static final int STYLE_NORMAL = 0;
+                        public static final int STYLE_NO_TITLE = 1;
+                        public static final int STYLE_NO_FRAME = 2;
+                        public static final int STYLE_NO_INPUT = 3;
+                        public static final int STYLE_UNRELATED = 3;
+
+                        public void setStyle(@DialogStyle int style, int theme) {
+                        }
+
+                        @IntDef(value = {STYLE_NORMAL, STYLE_NO_TITLE, STYLE_NO_FRAME, STYLE_NO_INPUT, 2, 3 + 1},
+                        flag=true)
+                        @Retention(RetentionPolicy.SOURCE)
+                        private @interface DialogFlags {}
+
+                        public void setFlags(Object first, @DialogFlags int flags) {
+                        }
+                    }
+                    """
+                ),
+                intRangeAnnotationSource,
+                intDefAnnotationSource
+            ),
+            checkCompilation = true,
+            checkDoclava1 = false,
+            stubs = arrayOf(
+                """
+                package test.pkg;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public class TypedefTest {
+                public TypedefTest() { throw new RuntimeException("Stub!"); }
+                /**
+                 * @param style Value is {@link test.pkg.TypedefTest#STYLE_NORMAL}, {@link test.pkg.TypedefTest#STYLE_NO_TITLE}, {@link test.pkg.TypedefTest#STYLE_NO_FRAME}, or {@link test.pkg.TypedefTest#STYLE_NO_INPUT}
+                 */
+                public void setStyle(int style, int theme) { throw new RuntimeException("Stub!"); }
+                /**
+                 * @param flags Value is either <code>0</code> or a combination of {@link test.pkg.TypedefTest#STYLE_NORMAL}, {@link test.pkg.TypedefTest#STYLE_NO_TITLE}, {@link test.pkg.TypedefTest#STYLE_NO_FRAME}, {@link test.pkg.TypedefTest#STYLE_NO_INPUT}, 2, and 3 + 1
+                 */
+                public void setFlags(java.lang.Object first, int flags) { throw new RuntimeException("Stub!"); }
+                public static final int STYLE_NORMAL = 0; // 0x0
+                public static final int STYLE_NO_FRAME = 2; // 0x2
+                public static final int STYLE_NO_INPUT = 3; // 0x3
+                public static final int STYLE_NO_TITLE = 1; // 0x1
+                public static final int STYLE_UNRELATED = 3; // 0x3
+                }
+                """
+            )
+        )
+    }
+
+    @Test
+    fun `Typedefs combined with ranges`() {
+        check(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+
+                    import android.annotation.IntDef;
+                    import android.annotation.IntRange;
+
+                    import java.lang.annotation.Retention;
+                    import java.lang.annotation.RetentionPolicy;
+
+                    @SuppressWarnings({"UnusedDeclaration", "WeakerAccess"})
+                    public class TypedefTest {
+                        @IntDef({STYLE_NORMAL, STYLE_NO_TITLE, STYLE_NO_FRAME, STYLE_NO_INPUT})
+                        @IntRange(from = 20)
+                        @Retention(RetentionPolicy.SOURCE)
+                        private @interface DialogStyle {}
+
+                        public static final int STYLE_NORMAL = 0;
+                        public static final int STYLE_NO_TITLE = 1;
+                        public static final int STYLE_NO_FRAME = 2;
+
+                        public void setStyle(@DialogStyle int style, int theme) {
+                        }
+                    }
+                    """
+                ),
+                intRangeAnnotationSource,
+                intDefAnnotationSource
+            ),
+            checkCompilation = true,
+            checkDoclava1 = false,
+            stubs = arrayOf(
+                """
+                package test.pkg;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public class TypedefTest {
+                public TypedefTest() { throw new RuntimeException("Stub!"); }
+                /**
+                 * @param style Value is {@link test.pkg.TypedefTest#STYLE_NORMAL}, {@link test.pkg.TypedefTest#STYLE_NO_TITLE}, {@link test.pkg.TypedefTest#STYLE_NO_FRAME}, or STYLE_NO_INPUT
+                 * Value is 20 or greater
+                 */
+                public void setStyle(int style, int theme) { throw new RuntimeException("Stub!"); }
+                public static final int STYLE_NORMAL = 0; // 0x0
+                public static final int STYLE_NO_FRAME = 2; // 0x2
+                public static final int STYLE_NO_TITLE = 1; // 0x1
+                }
+                """
+            )
+        )
+    }
+
+    @Test
+    fun `Create method documentation from nothing`() {
+        check(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    import android.annotation.RequiresPermission;
+                    @SuppressWarnings("WeakerAccess")
+                    public class RangeTest {
+                        public static final String ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION";
+                        @RequiresPermission(ACCESS_COARSE_LOCATION)
+                        public void test1() {
+                        }
+                    }
+                    """
+                ),
+                requiresPermissionSource
+            ),
+            checkCompilation = true,
+            checkDoclava1 = false,
+            stubs = arrayOf(
+                """
+                package test.pkg;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public class RangeTest {
+                public RangeTest() { throw new RuntimeException("Stub!"); }
+                /**
+                 * Requires {@link test.pkg.RangeTest#ACCESS_COARSE_LOCATION}
+                 */
+                @android.support.annotation.RequiresPermission(test.pkg.RangeTest.ACCESS_COARSE_LOCATION) public void test1() { throw new RuntimeException("Stub!"); }
+                public static final java.lang.String ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION";
+                }
+                """
+            )
+        )
+    }
+
+    @Test
+    fun `Warn about missing field`() {
+        check(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    import android.annotation.RequiresPermission;
+                    public class RangeTest {
+                        @RequiresPermission("MyPermission")
+                        public void test1() {
+                        }
+                    }
+                    """
+                ),
+                requiresPermissionSource
+            ),
+            checkCompilation = true,
+            checkDoclava1 = false,
+            warnings = "src/test/pkg/RangeTest.java:3: lint: Cannot find permission field for \"MyPermission\" required by method test.pkg.RangeTest.test1() (may be hidden or removed) [MissingPermission:132]",
+            stubs = arrayOf(
+                """
+                package test.pkg;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public class RangeTest {
+                public RangeTest() { throw new RuntimeException("Stub!"); }
+                /**
+                 * Requires "MyPermission"
+                 */
+                @android.support.annotation.RequiresPermission("MyPermission") public void test1() { throw new RuntimeException("Stub!"); }
+                }
+                """
+            )
+        )
+    }
+
+    @Test
+    fun `Add to existing single-line method documentation`() {
+        check(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    import android.annotation.RequiresPermission;
+                    @SuppressWarnings("WeakerAccess")
+                    public class RangeTest {
+                        public static final String ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION";
+                        /** This is the existing documentation. */
+                        @RequiresPermission(ACCESS_COARSE_LOCATION)
+                        public int test1() { }
+                    }
+                    """
+                ),
+                requiresPermissionSource
+            ),
+            checkCompilation = true,
+            checkDoclava1 = false,
+            stubs = arrayOf(
+                """
+                package test.pkg;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public class RangeTest {
+                public RangeTest() { throw new RuntimeException("Stub!"); }
+                /**
+                 * This is the existing documentation.
+                 * Requires {@link test.pkg.RangeTest#ACCESS_COARSE_LOCATION}
+                 */
+                @android.support.annotation.RequiresPermission(test.pkg.RangeTest.ACCESS_COARSE_LOCATION) public int test1() { throw new RuntimeException("Stub!"); }
+                public static final java.lang.String ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION";
+                }
+                """
+            )
+        )
+    }
+
+    @Test
+    fun `Add to existing multi-line method documentation`() {
+        check(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    import android.annotation.RequiresPermission;
+                    @SuppressWarnings("WeakerAccess")
+                    public class RangeTest {
+                        public static final String ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION";
+                        /**
+                         * This is the existing documentation.
+                         * Multiple lines of it.
+                         */
+                        @RequiresPermission(ACCESS_COARSE_LOCATION)
+                        public int test1() { }
+                    }
+                    """
+                ),
+                requiresPermissionSource
+            ),
+            checkCompilation = true,
+            checkDoclava1 = false,
+            stubs = arrayOf(
+                """
+                package test.pkg;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public class RangeTest {
+                public RangeTest() { throw new RuntimeException("Stub!"); }
+                /**
+                 * This is the existing documentation.
+                 * Multiple lines of it.
+                 * Requires {@link test.pkg.RangeTest#ACCESS_COARSE_LOCATION}
+                 */
+                @android.support.annotation.RequiresPermission(test.pkg.RangeTest.ACCESS_COARSE_LOCATION) public int test1() { throw new RuntimeException("Stub!"); }
+                public static final java.lang.String ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION";
+                }
+                """
+            )
+        )
+    }
+
+    @Test
+    fun `Add new parameter when no doc exists`() {
+        check(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    import android.annotation.IntRange;
+                    public class RangeTest {
+                        public int test1(int parameter1, @IntRange(from = 10) int parameter2, int parameter3) { }
+                    }
+                    """
+                ),
+                intRangeAnnotationSource
+            ),
+            checkCompilation = true,
+            checkDoclava1 = false,
+            stubs = arrayOf(
+                """
+                package test.pkg;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public class RangeTest {
+                public RangeTest() { throw new RuntimeException("Stub!"); }
+                /**
+                 * @param parameter2 Value is 10 or greater
+                 */
+                public int test1(int parameter1, @android.support.annotation.IntRange(from=10) int parameter2, int parameter3) { throw new RuntimeException("Stub!"); }
+                }
+                """
+            )
+        )
+    }
+
+    @Test
+    fun `Add to method when there are existing parameter docs and appear before these`() {
+        check(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    import android.annotation.RequiresPermission;
+                    @SuppressWarnings("WeakerAccess")
+                    public class RangeTest {
+                        public static final String ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION";
+                        /**
+                        * This is the existing documentation.
+                        * @param parameter1 docs for parameter1
+                        * @param parameter2 docs for parameter2
+                        * @param parameter3 docs for parameter2
+                        * @return return value documented here
+                        */
+                        @RequiresPermission(ACCESS_COARSE_LOCATION)
+                        public int test1(int parameter1, int parameter2, int parameter3) { }
+                    }
+                        """
+                ),
+                requiresPermissionSource
+            ),
+            checkCompilation = true,
+            checkDoclava1 = false,
+            stubs = arrayOf(
+                """
+                package test.pkg;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public class RangeTest {
+                public RangeTest() { throw new RuntimeException("Stub!"); }
+                /**
+                 * This is the existing documentation.
+                 * Requires {@link test.pkg.RangeTest#ACCESS_COARSE_LOCATION}
+                 * @param parameter1 docs for parameter1
+                 * @param parameter2 docs for parameter2
+                 * @param parameter3 docs for parameter2
+                 * @return return value documented here
+                 */
+                @android.support.annotation.RequiresPermission(test.pkg.RangeTest.ACCESS_COARSE_LOCATION) public int test1(int parameter1, int parameter2, int parameter3) { throw new RuntimeException("Stub!"); }
+                public static final java.lang.String ACCESS_COARSE_LOCATION = "android.permission.ACCESS_COARSE_LOCATION";
+                }
+                """
+            )
+        )
+    }
+
+    @Test
+    fun `Add new parameter when doc exists but no param doc`() {
+        check(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    import android.annotation.IntRange;
+                    public class RangeTest {
+                        /**
+                        * This is the existing documentation.
+                        * @return return value documented here
+                        */
+                        public int test1(int parameter1, @IntRange(from = 10) int parameter2, int parameter3) { }
+                    }
+                    """
+                ),
+                intRangeAnnotationSource
+            ),
+            checkCompilation = true,
+            checkDoclava1 = false,
+            stubs = arrayOf(
+                """
+                package test.pkg;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public class RangeTest {
+                public RangeTest() { throw new RuntimeException("Stub!"); }
+                /**
+                 * This is the existing documentation.
+                 * @param parameter2 Value is 10 or greater
+                 * @return return value documented here
+                 */
+                public int test1(int parameter1, @android.support.annotation.IntRange(from=10) int parameter2, int parameter3) { throw new RuntimeException("Stub!"); }
+                }
+                """
+            )
+        )
+    }
+
+    @Test
+    fun `Add new parameter, sorted correctly between existing ones`() {
+        check(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    import android.annotation.IntRange;
+                    public class RangeTest {
+                        /**
+                        * This is the existing documentation.
+                        * @param parameter1 docs for parameter1
+                        * @param parameter3 docs for parameter2
+                        * @return return value documented here
+                        */
+                        public int test1(int parameter1, @IntRange(from = 10) int parameter2, int parameter3) { }
+                    }
+                    """
+                ),
+                intRangeAnnotationSource
+            ),
+            checkCompilation = true,
+            checkDoclava1 = false,
+            stubs = arrayOf(
+                """
+                package test.pkg;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public class RangeTest {
+                public RangeTest() { throw new RuntimeException("Stub!"); }
+                /**
+                 * This is the existing documentation.
+                 * @param parameter1 docs for parameter1
+                 * @param parameter3 docs for parameter2
+                 * @param parameter2 Value is 10 or greater
+                 * @return return value documented here
+                 */
+                public int test1(int parameter1, @android.support.annotation.IntRange(from=10) int parameter2, int parameter3) { throw new RuntimeException("Stub!"); }
+                }
+                """
+            )
+        )
+    }
+
+    @Test
+    fun `Add to existing parameter`() {
+        check(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    import android.annotation.IntRange;
+                    public class RangeTest {
+                        /**
+                        * This is the existing documentation.
+                        * @param parameter1 docs for parameter1
+                        * @param parameter2 docs for parameter2
+                        * @param parameter3 docs for parameter2
+                        * @return return value documented here
+                        */
+                        public int test1(int parameter1, @IntRange(from = 10) int parameter2, int parameter3) { }
+                    }
+                    """
+                ),
+                intRangeAnnotationSource
+            ),
+            checkCompilation = true,
+            checkDoclava1 = false,
+            stubs = arrayOf(
+                """
+                package test.pkg;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public class RangeTest {
+                public RangeTest() { throw new RuntimeException("Stub!"); }
+                /**
+                 * This is the existing documentation.
+                 * @param parameter1 docs for parameter1
+                 * @param parameter2 docs for parameter2
+                 * Value is 10 or greater
+                 * @param parameter3 docs for parameter2
+                 * @return return value documented here
+                 */
+                public int test1(int parameter1, @android.support.annotation.IntRange(from=10) int parameter2, int parameter3) { throw new RuntimeException("Stub!"); }
+                }
+                """
+            )
+        )
+    }
+
+    @Test
+    fun `Add new return value`() {
+        check(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    import android.annotation.IntRange;
+                    public class RangeTest {
+                        @IntRange(from = 10)
+                        public int test1(int parameter1, int parameter2, int parameter3) { }
+                    }
+                    """
+                ),
+                intRangeAnnotationSource
+            ),
+            checkCompilation = true,
+            checkDoclava1 = false,
+            stubs = arrayOf(
+                """
+                package test.pkg;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public class RangeTest {
+                public RangeTest() { throw new RuntimeException("Stub!"); }
+                /**
+                 * @return Value is 10 or greater
+                 */
+                @android.support.annotation.IntRange(from=10) public int test1(int parameter1, int parameter2, int parameter3) { throw new RuntimeException("Stub!"); }
+                }
+                """
+            )
+        )
+    }
+
+    @Test
+    fun `Add to existing return value (ensuring it appears last)`() {
+        check(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    import android.annotation.IntRange;
+                    public class RangeTest {
+                        /**
+                        * This is the existing documentation.
+                        * @return return value documented here
+                        */
+                        @IntRange(from = 10)
+                        public int test1(int parameter1, int parameter2, int parameter3) { }
+                    }
+                    """
+                ),
+                intRangeAnnotationSource
+            ),
+            checkCompilation = true,
+            checkDoclava1 = false,
+            stubs = arrayOf(
+                """
+                package test.pkg;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public class RangeTest {
+                public RangeTest() { throw new RuntimeException("Stub!"); }
+                /**
+                 * This is the existing documentation.
+                 * @return return value documented here
+                 * Value is 10 or greater
+                 */
+                @android.support.annotation.IntRange(from=10) public int test1(int parameter1, int parameter2, int parameter3) { throw new RuntimeException("Stub!"); }
+                }
+                """
+            )
+        )
+    }
+
+    @Test
+    fun `test documentation trim utility`() {
+        assertEquals(
+            "/**\n * This is a comment\n * This is a second comment\n */",
+            trimDocIndent(
+                """/**
+         * This is a comment
+         * This is a second comment
+         */
+        """.trimIndent()
+            )
+        )
+    }
+
+    @Test
+    fun `Merge API levels)`() {
+        check(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package android.widget;
+
+                    public class Toolbar {
+                        public int getCurrentContentInsetEnd() {
+                            return 0;
+                        }
+                    }
+                    """
+                ),
+                intRangeAnnotationSource
+            ),
+            checkCompilation = true,
+            checkDoclava1 = false,
+            applyApiLevelsXml = """
+                    <?xml version="1.0" encoding="utf-8"?>
+                    <api version="2">
+                        <class name="android/widget/Toolbar" since="21">
+                            <method name="&lt;init>(Landroid/content/Context;)V"/>
+                            <method name="collapseActionView()V"/>
+                            <method name="getContentInsetStartWithNavigation()I" since="24"/>
+                            <method name="getCurrentContentInsetEnd()I" since="24"/>
+                            <method name="getCurrentContentInsetLeft()I" since="24"/>
+                            <method name="getCurrentContentInsetRight()I" since="24"/>
+                            <method name="getCurrentContentInsetStart()I" since="24"/>
+                        </class>
+                    </api>
+                    """,
+            stubs = arrayOf(
+                """
+                package android.widget;
+                /**
+                 * Requires API level 21
+                 */
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public class Toolbar {
+                public Toolbar() { throw new RuntimeException("Stub!"); }
+                /**
+                 * Requires API level 24
+                 */
+                public int getCurrentContentInsetEnd() { throw new RuntimeException("Stub!"); }
+                }
+                """
+            )
+        )
+    }
+
+    @Test
+    fun `Merge deprecation levels)`() {
+        check(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package android.hardware;
+                    /**
+                     * The Camera class is used to set image capture settings, start/stop preview.
+                     *
+                     * @deprecated We recommend using the new {@link android.hardware.camera2} API for new
+                     *             applications.*
+                    */
+                    @Deprecated
+                    public class Camera {
+                       /** @deprecated Use something else. */
+                       public static final String ACTION_NEW_VIDEO = "android.hardware.action.NEW_VIDEO";
+                    }
+                    """
+                )
+            ),
+            applyApiLevelsXml = """
+                    <?xml version="1.0" encoding="utf-8"?>
+                    <api version="2">
+                        <class name="android/hardware/Camera" since="1" deprecated="21">
+                            <method name="&lt;init>()V"/>
+                            <method name="addCallbackBuffer([B)V" since="8"/>
+                            <method name="getLogo()Landroid/graphics/drawable/Drawable;"/>
+                            <field name="ACTION_NEW_VIDEO" since="14" deprecated="25"/>
+                        </class>
+                    </api>
+                    """,
+            checkCompilation = true,
+            checkDoclava1 = false,
+            stubs = arrayOf(
+                """
+                package android.hardware;
+                /**
+                 * The Camera class is used to set image capture settings, start/stop preview.
+                 *
+                 * @deprecated
+                 * <p class="caution"><strong>This class was deprecated in API level 21.</strong></p>
+                 *  We recommend using the new {@link android.hardware.camera2} API for new
+                 *             applications.*
+                 */
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                @Deprecated public class Camera {
+                public Camera() { throw new RuntimeException("Stub!"); }
+                /**
+                 *
+                 * Requires API level 14
+                 * @deprecated
+                 * <p class="caution"><strong>This class was deprecated in API level 21.</strong></p>
+                 *  Use something else. */
+                @Deprecated public static final java.lang.String ACTION_NEW_VIDEO = "android.hardware.action.NEW_VIDEO";
+                }
+                """
+            )
+        )
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/metalava/DriverTest.kt b/src/test/java/com/android/tools/metalava/DriverTest.kt
new file mode 100644
index 0000000..281aab7
--- /dev/null
+++ b/src/test/java/com/android/tools/metalava/DriverTest.kt
@@ -0,0 +1,1160 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava
+
+import com.android.SdkConstants
+import com.android.SdkConstants.DOT_JAVA
+import com.android.SdkConstants.DOT_KT
+import com.android.SdkConstants.VALUE_TRUE
+import com.android.annotations.NonNull
+import com.android.ide.common.process.DefaultProcessExecutor
+import com.android.ide.common.process.LoggedProcessOutputHandler
+import com.android.ide.common.process.ProcessException
+import com.android.ide.common.process.ProcessInfoBuilder
+import com.android.tools.lint.checks.ApiLookup
+import com.android.tools.lint.checks.infrastructure.LintDetectorTest
+import com.android.tools.lint.checks.infrastructure.TestFile
+import com.android.tools.lint.checks.infrastructure.TestFiles
+import com.android.tools.lint.checks.infrastructure.TestFiles.java
+import com.android.tools.lint.checks.infrastructure.stripComments
+import com.android.tools.metalava.doclava1.Errors
+import com.android.utils.FileUtils
+import com.android.utils.StdLogger
+import com.google.common.base.Charsets
+import com.google.common.io.Files
+import org.intellij.lang.annotations.Language
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertTrue
+import org.junit.Assert.fail
+import org.junit.Rule
+import org.junit.rules.TemporaryFolder
+import java.io.File
+import java.io.PrintWriter
+import java.io.StringWriter
+
+const val CHECK_OLD_DOCLAVA_TOO = false
+const val CHECK_STUB_COMPILATION = false
+const val SKIP_NON_COMPAT = false
+
+abstract class DriverTest {
+    @get:Rule
+    var temporaryFolder = TemporaryFolder()
+
+    protected fun createProject(vararg files: TestFile): File {
+        val dir = temporaryFolder.newFolder()
+
+        files
+            .map { it.createFile(dir) }
+            .forEach { assertNotNull(it) }
+
+        return dir
+    }
+
+    protected fun runDriver(vararg args: String): String {
+        val sw = StringWriter()
+        val writer = PrintWriter(sw)
+        if (!com.android.tools.metalava.run(arrayOf(*args), writer, writer)) {
+            fail(sw.toString())
+        }
+
+        return sw.toString()
+    }
+
+    private fun findKotlinStdlibPath(): List<String> {
+        val classPath: String = System.getProperty("java.class.path")
+        val paths = mutableListOf<String>()
+        for (path in classPath.split(':')) {
+            val file = File(path)
+            val name = file.name
+            if (name.startsWith("kotlin-stdlib") ||
+                name.startsWith("kotlin-reflect") ||
+                name.startsWith("kotlin-script-runtime")
+            ) {
+                paths.add(file.path)
+            }
+        }
+        if (paths.isEmpty()) {
+            error("Did not find kotlin-stdlib-jre8 in $PROGRAM_NAME classpath: $classPath")
+        }
+        return paths
+    }
+
+    private fun getJdkPath(): String? {
+        val javaHome = System.getProperty("java.home")
+        if (javaHome != null) {
+            var javaHomeFile = File(javaHome)
+            if (File(javaHomeFile, "bin${File.separator}javac").exists()) {
+                return javaHome
+            } else if (javaHomeFile.name == "jre") {
+                javaHomeFile = javaHomeFile.parentFile
+                if (javaHomeFile != null && File(javaHomeFile, "bin${File.separator}javac").exists()) {
+                    return javaHomeFile.path
+                }
+            }
+        }
+        return System.getenv("JAVA_HOME")
+    }
+
+    protected fun check(
+        /** The source files to pass to the analyzer */
+        vararg sourceFiles: TestFile,
+        /** The API signature content (corresponds to --api) */
+        api: String? = null,
+        /** The exact API signature content (corresponds to --exact-api) */
+        exactApi: String? = null,
+        /** The removed API (corresponds to --removed-api) */
+        removedApi: String? = null,
+        /** The private API (corresponds to --private-api) */
+        privateApi: String? = null,
+        /** The private DEX API (corresponds to --private-dex-api) */
+        privateDexApi: String? = null,
+        /** Expected stubs (corresponds to --stubs) */
+        @Language("JAVA") stubs: Array<String> = emptyArray(),
+        /** Stub source file list generated */
+        stubsSourceList: String? = null,
+        /** Whether to run in doclava1 compat mode */
+        compatibilityMode: Boolean = true,
+        /** Whether to trim the output (leading/trailing whitespace removal) */
+        trim: Boolean = true,
+        /** Whether to remove blank lines in the output (the signature file usually contains a lot of these) */
+        stripBlankLines: Boolean = true,
+        /** Warnings expected to be generated when analyzing these sources */
+        warnings: String? = "",
+        /** Whether to run doclava1 on the test output and assert that the output is identical */
+        checkDoclava1: Boolean = compatibilityMode,
+        checkCompilation: Boolean = false,
+        /** Annotations to merge in */
+        @Language("XML") mergeAnnotations: String? = null,
+        /** An optional API signature file content to load **instead** of Java/Kotlin source files */
+        @Language("TEXT") signatureSource: String? = null,
+        /** An optional API signature representing the previous API level to diff */
+        @Language("TEXT") previousApi: String? = null,
+        /** An optional Proguard keep file to generate */
+        @Language("Proguard") proguard: String? = null,
+        /** Whether we should migrate nullness information */
+        migrateNulls: Boolean = false,
+        /** Whether we should check compatibility */
+        checkCompatibility: Boolean = false,
+        /** Show annotations (--show-annotation arguments) */
+        showAnnotations: Array<String> = emptyArray(),
+        /** If using [showAnnotations], whether to include unannotated */
+        showUnannotated: Boolean = false,
+        /** Additional arguments to supply */
+        extraArguments: Array<String> = emptyArray(),
+        /** Whether we should emit Kotlin-style null signatures */
+        outputKotlinStyleNulls: Boolean = !compatibilityMode,
+        /** Whether we should interpret API files being read as having Kotlin-style nullness types */
+        inputKotlinStyleNulls: Boolean = false,
+        /** Whether we should omit java.lang. etc from signature files */
+        omitCommonPackages: Boolean = !compatibilityMode,
+        /** Expected output (stdout and stderr combined). If null, don't check. */
+        expectedOutput: String? = null,
+        /** List of extra jar files to record annotation coverage from */
+        coverageJars: Array<TestFile>? = null,
+        /** Optional manifest to load and associate with the codebase */
+        @Language("XML")
+        manifest: String? = null,
+        /** Packages to pre-import (these will therefore NOT be included in emitted stubs, signature files etc */
+        importedPackages: List<String> = emptyList(),
+        /** Packages to skip emitting signatures/stubs for even if public (typically used for unit tests
+         * referencing to classpath classes that aren't part of the definitions and shouldn't be part of the
+         * test output; e.g. a test may reference java.lang.Enum but we don't want to start reporting all the
+         * public APIs in the java.lang package just because it's indirectly referenced via the "enum" superclass
+         */
+        skipEmitPackages: List<String> = listOf("java.lang", "java.util", "java.io"),
+        /** Whether we should include --showAnnotations=android.annotation.SystemApi */
+        includeSystemApiAnnotations: Boolean = false,
+        /** Whether we should warn about super classes that are stripped because they are hidden */
+        includeStrippedSuperclassWarnings: Boolean = false,
+        /** Apply level to XML */
+        applyApiLevelsXml: String? = null
+    ) {
+        System.setProperty("METALAVA_TESTS_RUNNING", VALUE_TRUE)
+
+        if (compatibilityMode && mergeAnnotations != null) {
+            fail(
+                "Can't specify both compatibilityMode and mergeAnnotations: there were no " +
+                        "annotations output in doclava1"
+            )
+        }
+
+        Errors.resetLevels()
+
+        // Unit test which checks that a signature file is as expected
+        val androidJar = getPlatformFile("android.jar")
+
+        val project = createProject(*sourceFiles)
+
+        val packages = sourceFiles.asSequence().map { findPackage(it.getContents()!!) }.filterNotNull().toSet()
+
+        val sourcePathDir = File(project, "src")
+        val sourcePath = sourcePathDir.path
+        val sourceList =
+            if (signatureSource != null) {
+                sourcePathDir.mkdirs()
+                assert(sourceFiles.isEmpty(), { "Shouldn't combine sources with signature file loads" })
+                val signatureFile = File(project, "load-api.txt")
+                Files.asCharSink(signatureFile, Charsets.UTF_8).write(signatureSource.trimIndent())
+                if (includeStrippedSuperclassWarnings) {
+                    arrayOf(signatureFile.path)
+                } else {
+                    arrayOf(
+                        signatureFile.path,
+                        "--hide",
+                        "HiddenSuperclass"
+                    ) // Suppress warning #111
+                }
+            } else {
+                sourceFiles.asSequence().map { File(project, it.targetPath).path }.toList().toTypedArray()
+            }
+
+        val reportedWarnings = StringBuilder()
+        reporter = object : Reporter(project) {
+            override fun print(message: String) {
+                reportedWarnings.append(message.replace(project.path, "TESTROOT").trim()).append('\n')
+            }
+        }
+
+        val mergeAnnotationsArgs = if (mergeAnnotations != null) {
+            val merged = File(project, "merged-annotations.xml")
+            Files.asCharSink(merged, Charsets.UTF_8).write(mergeAnnotations.trimIndent())
+            arrayOf("--merge-annotations", merged.path)
+        } else {
+            emptyArray()
+        }
+
+        val previousApiFile = if (previousApi != null) {
+            val file = File(project, "previous-api.txt")
+            Files.asCharSink(file, Charsets.UTF_8).write(previousApi.trimIndent())
+            file
+        } else {
+            null
+        }
+
+        val previousApiArgs = if (previousApiFile != null) {
+            arrayOf("--previous-api", previousApiFile.path)
+        } else {
+            emptyArray()
+        }
+
+        val manifestFileArgs = if (manifest != null) {
+            val file = File(project, "manifest.xml")
+            Files.asCharSink(file, Charsets.UTF_8).write(manifest.trimIndent())
+            arrayOf("--manifest", file.path)
+        } else {
+            emptyArray()
+        }
+
+        val migrateNullsArguments = if (migrateNulls) {
+            arrayOf("--migrate-nullness")
+        } else {
+            emptyArray()
+        }
+
+        val checkCompatibilityArguments = if (checkCompatibility) {
+            arrayOf("--check-compatibility")
+        } else {
+            emptyArray()
+        }
+
+        val quiet = if (expectedOutput != null && !extraArguments.contains("--verbose")) {
+            // If comparing output, avoid noisy output such as the banner etc
+            arrayOf("--quiet")
+        } else {
+            emptyArray()
+        }
+
+        val coverageStats = if (coverageJars != null && coverageJars.isNotEmpty()) {
+            val sb = StringBuilder()
+            val root = File(project, "coverageJars")
+            root.mkdirs()
+            for (jar in coverageJars) {
+                if (sb.isNotEmpty()) {
+                    sb.append(File.pathSeparator)
+                }
+                val file = jar.createFile(root)
+                sb.append(file.path)
+            }
+            arrayOf("--annotation-coverage-of", sb.toString())
+        } else {
+            emptyArray()
+        }
+
+        var proguardFile: File? = null
+        val proguardKeepArguments = if (proguard != null) {
+            proguardFile = File(project, "proguard.cfg")
+            arrayOf("--proguard", proguardFile.path)
+        } else {
+            emptyArray()
+        }
+
+        val showAnnotationArguments = if (showAnnotations.isNotEmpty() || includeSystemApiAnnotations) {
+            val args = mutableListOf<String>()
+            for (annotation in showAnnotations) {
+                args.add("--show-annotation")
+                args.add(annotation)
+            }
+            if (includeSystemApiAnnotations && !args.contains("android.annotation.SystemApi")) {
+                args.add("--show-annotation")
+                args.add("android.annotation.SystemApi")
+            }
+            args.toTypedArray()
+        } else {
+            emptyArray()
+        }
+
+        val showUnannotatedArgs =
+            if (showUnannotated) {
+                arrayOf("--show-unannotated")
+            } else {
+                emptyArray<String>()
+            }
+
+        var removedApiFile: File? = null
+        val removedArgs = if (removedApi != null) {
+            removedApiFile = temporaryFolder.newFile("removed.txt")
+            arrayOf("--removed-api", removedApiFile.path)
+        } else {
+            emptyArray()
+        }
+
+        var apiFile: File? = null
+        val apiArgs = if (api != null) {
+            apiFile = temporaryFolder.newFile("api.txt")
+            arrayOf("--api", apiFile.path)
+        } else {
+            emptyArray()
+        }
+
+        var exactApiFile: File? = null
+        val exactApiArgs = if (exactApi != null) {
+            exactApiFile = temporaryFolder.newFile("exact-api.txt")
+            arrayOf("--exact-api", exactApiFile.path)
+        } else {
+            emptyArray()
+        }
+
+        var privateApiFile: File? = null
+        val privateApiArgs = if (privateApi != null) {
+            privateApiFile = temporaryFolder.newFile("private.txt")
+            arrayOf("--private-api", privateApiFile.path)
+        } else {
+            emptyArray()
+        }
+
+        var privateDexApiFile: File? = null
+        val privateDexApiArgs = if (privateDexApi != null) {
+            privateDexApiFile = temporaryFolder.newFile("private-dex.txt")
+            arrayOf("--private-dex-api", privateDexApiFile.path)
+        } else {
+            emptyArray()
+        }
+
+        var stubsDir: File? = null
+        val stubsArgs = if (stubs.isNotEmpty()) {
+            stubsDir = temporaryFolder.newFolder("stubs")
+            arrayOf("--stubs", stubsDir.path)
+        } else {
+            emptyArray()
+        }
+
+        var stubsSourceListFile: File? = null
+        val stubsSourceListArgs = if (stubsSourceList != null) {
+            stubsSourceListFile = temporaryFolder.newFile("droiddoc-src-list")
+            arrayOf("--write-stubs-source-list", stubsSourceListFile.path)
+        } else {
+            emptyArray()
+        }
+
+        val applyApiLevelsXmlFile: File?
+        val applyApiLevelsXmlArgs = if (applyApiLevelsXml != null) {
+            ApiLookup::class.java.getDeclaredMethod("dispose").apply { isAccessible = true }.invoke(null)
+            applyApiLevelsXmlFile = temporaryFolder.newFile("api-versions.xml")
+            Files.asCharSink(applyApiLevelsXmlFile!!, Charsets.UTF_8).write(applyApiLevelsXml.trimIndent())
+            arrayOf("--apply-api-levels", applyApiLevelsXmlFile.path)
+        } else {
+            emptyArray()
+        }
+
+        val importedPackageArgs = mutableListOf<String>()
+        importedPackages.forEach {
+            importedPackageArgs.add("--stub-import-packages")
+            importedPackageArgs.add(it)
+        }
+
+        val skipEmitPackagesArgs = mutableListOf<String>()
+        skipEmitPackages.forEach {
+            skipEmitPackagesArgs.add("--skip-emit-packages")
+            skipEmitPackagesArgs.add(it)
+        }
+
+        val kotlinPath = findKotlinStdlibPath()
+        val kotlinPathArgs =
+            if (kotlinPath.isNotEmpty() &&
+                sourceList.asSequence().any { it.endsWith(DOT_KT) }
+            ) {
+                arrayOf("--classpath", kotlinPath.joinToString(separator = File.pathSeparator) { it })
+            } else {
+                emptyArray()
+            }
+
+        val actualOutput = runDriver(
+            "--no-color",
+
+            // For the tests we want to treat references to APIs like java.io.Closeable
+            // as a class that is part of the API surface, not as a hidden class as would
+            // be the case when analyzing a complete API surface
+            //"--unhide-classpath-classes",
+            "--allow-referencing-unknown-classes",
+
+            "--sourcepath",
+            sourcePath,
+            "--classpath",
+            androidJar.path,
+            *kotlinPathArgs,
+            *removedArgs,
+            *apiArgs,
+            *exactApiArgs,
+            *privateApiArgs,
+            *privateDexApiArgs,
+            *stubsArgs,
+            *stubsSourceListArgs,
+            "--compatible-output=${if (compatibilityMode) "yes" else "no"}",
+            "--output-kotlin-nulls=${if (outputKotlinStyleNulls) "yes" else "no"}",
+            "--input-kotlin-nulls=${if (inputKotlinStyleNulls) "yes" else "no"}",
+            "--omit-common-packages=${if (omitCommonPackages) "yes" else "no"}",
+            *coverageStats,
+            *quiet,
+            *mergeAnnotationsArgs,
+            *previousApiArgs,
+            *migrateNullsArguments,
+            *checkCompatibilityArguments,
+            *proguardKeepArguments,
+            *manifestFileArgs,
+            *applyApiLevelsXmlArgs,
+            *showAnnotationArguments,
+            *showUnannotatedArgs,
+            *importedPackageArgs.toTypedArray(),
+            *skipEmitPackagesArgs.toTypedArray(),
+            *extraArguments,
+            *sourceList
+        )
+
+        if (expectedOutput != null) {
+            assertEquals(expectedOutput.trimIndent().trim(), actualOutput.trim())
+        }
+
+        if (api != null && apiFile != null) {
+            assertTrue("${apiFile.path} does not exist even though --api was used", apiFile.exists())
+            val expectedText = readFile(apiFile, stripBlankLines, trim)
+            assertEquals(stripComments(api, stripLineComments = false).trimIndent(), expectedText)
+        }
+
+        if (removedApi != null && removedApiFile != null) {
+            assertTrue(
+                "${removedApiFile.path} does not exist even though --removed-api was used",
+                removedApiFile.exists()
+            )
+            val expectedText = readFile(removedApiFile, stripBlankLines, trim)
+            assertEquals(stripComments(removedApi, stripLineComments = false).trimIndent(), expectedText)
+        }
+
+        if (exactApi != null && exactApiFile != null) {
+            assertTrue(
+                "${exactApiFile.path} does not exist even though --exact-api was used",
+                exactApiFile.exists()
+            )
+            val expectedText = readFile(exactApiFile, stripBlankLines, trim)
+            assertEquals(stripComments(exactApi, stripLineComments = false).trimIndent(), expectedText)
+        }
+
+        if (privateApi != null && privateApiFile != null) {
+            assertTrue(
+                "${privateApiFile.path} does not exist even though --private-api was used",
+                privateApiFile.exists()
+            )
+            val expectedText = readFile(privateApiFile, stripBlankLines, trim)
+            assertEquals(stripComments(privateApi, stripLineComments = false).trimIndent(), expectedText)
+        }
+
+        if (privateDexApi != null && privateDexApiFile != null) {
+            assertTrue(
+                "${privateDexApiFile.path} does not exist even though --private-dex-api was used",
+                privateDexApiFile.exists()
+            )
+            val expectedText = readFile(privateDexApiFile, stripBlankLines, trim)
+            assertEquals(stripComments(privateDexApi, stripLineComments = false).trimIndent(), expectedText)
+        }
+
+        if (proguard != null && proguardFile != null) {
+            val expectedProguard = readFile(proguardFile)
+            assertTrue(
+                "${proguardFile.path} does not exist even though --proguard was used",
+                proguardFile.exists()
+            )
+            assertEquals(stripComments(proguard, stripLineComments = false).trimIndent(), expectedProguard.trim())
+        }
+
+        if (warnings != null) {
+            assertEquals(
+                warnings.trimIndent().trim(),
+                reportedWarnings.toString().replace(project.path, "TESTROOT").replace(
+                    project.canonicalPath,
+                    "TESTROOT"
+                ).split("\n").sorted().joinToString(separator = "\n").trim()
+            )
+        }
+
+        if (stubs.isNotEmpty() && stubsDir != null) {
+            for (i in 0 until stubs.size) {
+                val stub = stubs[i]
+                val sourceFile = sourceFiles[i]
+                val targetPath = if (sourceFile.targetPath.endsWith(DOT_KT)) {
+                    // Kotlin source stubs are rewritten as .java files for now
+                    sourceFile.targetPath.substring(0, sourceFile.targetPath.length - 3) + DOT_JAVA
+                } else {
+                    sourceFile.targetPath
+                }
+                val stubFile = File(stubsDir, targetPath.substring("src/".length))
+                val expectedText = readFile(stubFile, stripBlankLines, trim)
+                assertEquals(stub.trimIndent(), expectedText)
+            }
+        }
+
+        if (stubsSourceList != null && stubsSourceListFile != null) {
+            assertTrue(
+                "${stubsSourceListFile.path} does not exist even though --write-stubs-source-list was used",
+                stubsSourceListFile.exists()
+            )
+            val expectedText = readFile(stubsSourceListFile, stripBlankLines, trim)
+            assertEquals(stripComments(stubsSourceList, stripLineComments = false).trimIndent(), expectedText)
+        }
+
+        if (checkCompilation && stubsDir != null && CHECK_STUB_COMPILATION) {
+            val generated = gatherSources(listOf(stubsDir)).map { it.path }.toList().toTypedArray()
+
+            // Also need to include on the compile path annotation classes referenced in the stubs
+            val supportAnnotationsDir = File("../../frameworks/support/annotations/src/main/java/")
+            if (!supportAnnotationsDir.isDirectory) {
+                fail("Couldn't find $supportAnnotationsDir: Is the pwd set to the root of the metalava source code?")
+            }
+            val supportAnnotations =
+                gatherSources(listOf(supportAnnotationsDir)).map { it.path }.toList().toTypedArray()
+
+            val extraAnnotationsDir = File("stub-annotations/src/main/java")
+            if (!extraAnnotationsDir.isDirectory) {
+                fail("Couldn't find $extraAnnotationsDir: Is the pwd set to the root of the metalava source code?")
+                fail("Couldn't find $extraAnnotationsDir: Is the pwd set to the root of an Android source tree?")
+            }
+            val extraAnnotations = gatherSources(listOf(extraAnnotationsDir)).map { it.path }.toList().toTypedArray()
+
+
+            if (!runCommand(
+                    "${getJdkPath()}/bin/javac", arrayOf(
+                        "-d", project.path, *generated,
+                        *supportAnnotations, *extraAnnotations
+                    )
+                )
+            ) {
+                fail("Couldn't compile stub file -- compilation problems")
+                return
+            }
+        }
+
+        if (checkDoclava1 && !CHECK_OLD_DOCLAVA_TOO) {
+            println(
+                "This test requested diffing with doclava1, but doclava1 testing was disabled with the " +
+                        "DriverTest#CHECK_OLD_DOCLAVA_TOO = false"
+            )
+        }
+
+        if (CHECK_OLD_DOCLAVA_TOO && checkDoclava1 && signatureSource == null &&
+            api != null && apiFile != null
+        ) {
+            apiFile.delete()
+            checkSignaturesWithDoclava1(
+                api = api,
+                argument = "-api",
+                output = apiFile,
+                expected = apiFile,
+                sourceList = sourceList,
+                sourcePath = sourcePath,
+                packages = packages,
+                androidJar = androidJar,
+                trim = trim,
+                stripBlankLines = stripBlankLines,
+                showAnnotationArgs = showAnnotationArguments,
+                stubImportPackages = importedPackages,
+                showUnannotated = showUnannotated
+            )
+        }
+
+        if (CHECK_OLD_DOCLAVA_TOO && checkDoclava1 && signatureSource == null &&
+            exactApi != null && exactApiFile != null
+        ) {
+            exactApiFile.delete()
+            checkSignaturesWithDoclava1(
+                api = exactApi,
+                argument = "-exactApi",
+                output = exactApiFile,
+                expected = exactApiFile,
+                sourceList = sourceList,
+                sourcePath = sourcePath,
+                packages = packages,
+                androidJar = androidJar,
+                trim = trim,
+                stripBlankLines = stripBlankLines,
+                showAnnotationArgs = showAnnotationArguments,
+                stubImportPackages = importedPackages,
+                showUnannotated = showUnannotated
+            )
+        }
+
+        if (CHECK_OLD_DOCLAVA_TOO && checkDoclava1 && signatureSource == null
+            && removedApi != null && removedApiFile != null
+        ) {
+            removedApiFile.delete()
+            checkSignaturesWithDoclava1(
+                api = removedApi,
+                argument = "-removedApi",
+                output = removedApiFile,
+                expected = removedApiFile,
+                sourceList = sourceList,
+                sourcePath = sourcePath,
+                packages = packages,
+                androidJar = androidJar,
+                trim = trim,
+                stripBlankLines = stripBlankLines,
+                showAnnotationArgs = showAnnotationArguments,
+                stubImportPackages = importedPackages,
+                showUnannotated = showUnannotated
+            )
+        }
+
+        if (CHECK_OLD_DOCLAVA_TOO && checkDoclava1 && signatureSource == null && stubsDir != null) {
+            stubsDir.deleteRecursively()
+            val firstFile = File(stubsDir, sourceFiles[0].targetPath.substring("src/".length))
+            checkSignaturesWithDoclava1(
+                api = stubs[0],
+                argument = "-stubs",
+                output = stubsDir,
+                expected = firstFile,
+                sourceList = sourceList,
+                sourcePath = sourcePath,
+                packages = packages,
+                androidJar = androidJar,
+                trim = trim,
+                stripBlankLines = stripBlankLines,
+                showAnnotationArgs = showAnnotationArguments,
+                stubImportPackages = importedPackages,
+                showUnannotated = showUnannotated
+            )
+        }
+
+        if (CHECK_OLD_DOCLAVA_TOO && checkDoclava1 && proguard != null && proguardFile != null) {
+            proguardFile.delete()
+            checkSignaturesWithDoclava1(
+                api = proguard,
+                argument = "-proguard",
+                output = proguardFile,
+                expected = proguardFile,
+                sourceList = sourceList,
+                sourcePath = sourcePath,
+                packages = packages,
+                androidJar = androidJar,
+                trim = trim,
+                stripBlankLines = stripBlankLines,
+                showAnnotationArgs = showAnnotationArguments,
+                stubImportPackages = importedPackages,
+                showUnannotated = showUnannotated
+            )
+        }
+
+        if (CHECK_OLD_DOCLAVA_TOO && checkDoclava1 && signatureSource == null &&
+            privateApi != null && privateApiFile != null
+        ) {
+            privateApiFile.delete()
+            checkSignaturesWithDoclava1(
+                api = privateApi,
+                argument = "-privateApi",
+                output = privateApiFile,
+                expected = privateApiFile,
+                sourceList = sourceList,
+                sourcePath = sourcePath,
+                packages = packages,
+                androidJar = androidJar,
+                trim = trim,
+                stripBlankLines = stripBlankLines,
+                showAnnotationArgs = showAnnotationArguments,
+                stubImportPackages = importedPackages,
+                // Workaround: -privateApi is a no-op if you don't also provide -api
+                extraArguments = arrayOf("-api", File(privateApiFile.parentFile, "dummy-api.txt").path),
+                showUnannotated = showUnannotated
+            )
+        }
+
+        if (CHECK_OLD_DOCLAVA_TOO && checkDoclava1 && signatureSource == null &&
+            privateDexApi != null && privateDexApiFile != null
+        ) {
+            privateDexApiFile.delete()
+            checkSignaturesWithDoclava1(
+                api = privateDexApi,
+                argument = "-privateDexApi",
+                output = privateDexApiFile,
+                expected = privateDexApiFile,
+                sourceList = sourceList,
+                sourcePath = sourcePath,
+                packages = packages,
+                androidJar = androidJar,
+                trim = trim,
+                stripBlankLines = stripBlankLines,
+                showAnnotationArgs = showAnnotationArguments,
+                stubImportPackages = importedPackages,
+                // Workaround: -privateDexApi is a no-op if you don't also provide -api
+                extraArguments = arrayOf("-api", File(privateDexApiFile.parentFile, "dummy-api.txt").path),
+                showUnannotated = showUnannotated
+            )
+        }
+    }
+
+    private fun checkSignaturesWithDoclava1(
+        api: String,
+        argument: String,
+        output: File,
+        expected: File = output,
+        sourceList: Array<String>,
+        sourcePath: String,
+        packages: Set<String>,
+        androidJar: File,
+        trim: Boolean = true,
+        stripBlankLines: Boolean = true,
+        showAnnotationArgs: Array<String> = emptyArray(),
+        stubImportPackages: List<String>,
+        extraArguments: Array<String> = emptyArray(),
+        showUnannotated: Boolean
+    ) {
+        // We have to run Doclava out of process because running it in process
+        // (with Doclava1 jars on the test classpath) only works once; it leaves
+        // around state such that the second test fails. Instead we invoke it
+        // separately on each test; slower but reliable.
+
+        val doclavaArg = when (argument) {
+            "--api" -> "-api"
+            "--removed-api" -> "-removedApi"
+            else -> if (argument.startsWith("--")) argument.substring(1) else argument
+        }
+
+        val showAnnotationArgsDoclava1: Array<String> = if (showAnnotationArgs.isNotEmpty()) {
+            showAnnotationArgs.map { if (it == "--show-annotation") "-showAnnotation" else it }.toTypedArray()
+        } else {
+            emptyArray()
+        }
+        val showUnannotatedArgs = if (showUnannotated) {
+            arrayOf("-showUnannotated")
+        } else {
+            emptyArray()
+        }
+
+        val docLava1 = File("testlibs/doclava-1.0.6-full-SNAPSHOT.jar")
+        if (!docLava1.isFile) {
+            /*
+                Not checked in (it's 22MB).
+                To generate the doclava1 jar, add this to external/doclava/build.gradle and run ./gradlew shadowJar:
+
+                // shadow jar: Includes all dependencies
+                buildscript {
+                    repositories {
+                        jcenter()
+                    }
+                    dependencies {
+                        classpath 'com.github.jengelman.gradle.plugins:shadow:2.0.2'
+                    }
+                }
+                apply plugin: 'com.github.johnrengelman.shadow'
+                shadowJar {
+                   baseName = "doclava-$version-full-SNAPSHOT"
+                   classifier = null
+                   version = null
+                }
+             */
+            fail("Couldn't find $docLava1: Is the pwd set to the root of the metalava source code?")
+        }
+
+        val jdkPath = getJdkPath()
+        if (jdkPath == null) {
+            fail("JDK not found in the environment; make sure \$JAVA_HOME is set.")
+        }
+
+        val hidePackageArgs = mutableListOf<String>()
+        options.hidePackages.forEach {
+            hidePackageArgs.add("-hidePackage")
+            hidePackageArgs.add(it)
+        }
+
+        val stubImports = if (stubImportPackages.isNotEmpty()) {
+            arrayOf("-stubimportpackages", stubImportPackages.joinToString(separator = ":") { it })
+        } else {
+            emptyArray()
+        }
+
+        val args = arrayOf<String>(
+            *sourceList,
+            "-stubpackages",
+            packages.joinToString(separator = ":") { it },
+            *stubImports,
+            "-doclet",
+            "com.google.doclava.Doclava",
+            "-docletpath",
+            docLava1.path,
+            "-encoding",
+            "UTF-8",
+            "-source",
+            "1.8",
+            "-nodocs",
+            "-quiet",
+            "-sourcepath",
+            sourcePath,
+            "-classpath",
+            androidJar.path,
+
+            *showAnnotationArgsDoclava1,
+            *showUnannotatedArgs,
+            *hidePackageArgs.toTypedArray(),
+            *extraArguments,
+
+            // -api, or // -stub, etc
+            doclavaArg,
+            output.path
+        )
+
+
+        val message = "\n${args.joinToString(separator = "\n") { "\"$it\"," }}"
+        println("Running doclava1 with the following args:\n$message")
+
+
+        if (!runCommand(
+                "$jdkPath/bin/java",
+                arrayOf(
+                    "-classpath",
+                    "${docLava1.path}:$jdkPath/lib/tools.jar",
+                    "com.google.doclava.Doclava",
+                    *args
+                )
+            )
+        ) {
+            return
+        }
+
+        val expectedText = readFile(expected, stripBlankLines, trim)
+        assertEquals(stripComments(api, stripLineComments = false).trimIndent(), expectedText)
+    }
+
+    private fun runCommand(executable: String, args: Array<String>): Boolean {
+        try {
+            val logger = StdLogger(StdLogger.Level.ERROR)
+            val processExecutor = DefaultProcessExecutor(logger)
+            val processInfo = ProcessInfoBuilder()
+                .setExecutable(executable)
+                .addArgs(args)
+                .createProcess()
+
+            val processOutputHandler = LoggedProcessOutputHandler(logger)
+            val result = processExecutor.execute(processInfo, processOutputHandler)
+
+            result.rethrowFailure().assertNormalExitValue()
+        } catch (e: ProcessException) {
+            fail("Failed to run $executable (${e.message}): not verifying this API on the old doclava engine")
+            return false
+        }
+        return true
+    }
+
+    companion object {
+        private val apiLevel = "27"
+
+        private val latestAndroidPlatform: String
+            get() = "android-$apiLevel"
+
+        private val sdk: File
+            get() = File(
+                System.getenv("ANDROID_HOME")
+                        ?: error("You must set \$ANDROID_HOME before running tests")
+            )
+
+        fun getPlatformFile(path: String): File {
+            val localFile = File("../../prebuilts/sdk/$apiLevel/android.jar")
+            if (localFile.exists()) {
+                return localFile
+            }
+            val file = FileUtils.join(sdk, SdkConstants.FD_PLATFORMS, latestAndroidPlatform, path)
+            if (!file.exists()) {
+                throw IllegalArgumentException(
+                    "File \"$path\" not found in platform ${latestAndroidPlatform}"
+                )
+            }
+            return file
+        }
+
+        @NonNull
+        fun java(@NonNull @Language("JAVA") source: String): LintDetectorTest.TestFile {
+            return TestFiles.java(source.trimIndent())
+        }
+
+        @NonNull
+        fun kotlin(@NonNull @Language("kotlin") source: String): LintDetectorTest.TestFile {
+            return TestFiles.kotlin(source.trimIndent())
+        }
+
+        private fun readFile(file: File, stripBlankLines: Boolean = false, trim: Boolean = false): String {
+            var apiLines: List<String> = Files.asCharSource(file, Charsets.UTF_8).readLines()
+            if (stripBlankLines) {
+                apiLines = apiLines.asSequence().filter { it.isNotBlank() }.toList()
+            }
+            var apiText = apiLines.joinToString(separator = "\n") { it }
+            if (trim) {
+                apiText = apiText.trim()
+            }
+            return apiText
+        }
+    }
+}
+
+val intRangeAnnotationSource: TestFile = java(
+    """
+        package android.annotation;
+        import java.lang.annotation.*;
+        import static java.lang.annotation.ElementType.*;
+        import static java.lang.annotation.RetentionPolicy.SOURCE;
+        @Retention(SOURCE)
+        @Target({METHOD,PARAMETER,FIELD,LOCAL_VARIABLE,ANNOTATION_TYPE})
+        public @interface IntRange {
+            long from() default Long.MIN_VALUE;
+            long to() default Long.MAX_VALUE;
+        }
+                """
+).indented()
+
+val intDefAnnotationSource: TestFile = java(
+    """
+package android.annotation;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import static java.lang.annotation.ElementType.*;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+@Retention(SOURCE)
+@Target({ANNOTATION_TYPE})
+public @interface IntDef {
+    long[] value() default {};
+    boolean flag() default false;
+}
+"""
+)
+
+val nonNullSource: TestFile = java(
+    """
+package android.annotation;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+/**
+ * Denotes that a parameter, field or method return value can never be null.
+ * @paramDoc This value must never be {@code null}.
+ * @returnDoc This value will never be {@code null}.
+ * @hide
+ */
+@SuppressWarnings({"WeakerAccess", "JavaDoc"})
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD, TYPE_USE})
+public @interface NonNull {
+}
+
+"""
+)
+
+val requiresPermissionSource: TestFile = java(
+    """
+package android.annotation;
+import java.lang.annotation.*;
+import static java.lang.annotation.ElementType.*;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+@Retention(SOURCE)
+@Target({ANNOTATION_TYPE,METHOD,CONSTRUCTOR,FIELD,PARAMETER})
+public @interface RequiresPermission {
+    String value() default "";
+    String[] allOf() default {};
+    String[] anyOf() default {};
+    boolean conditional() default false;
+}
+                """
+)
+
+val nullableSource: TestFile = java(
+    """
+package android.annotation;
+import java.lang.annotation.*;
+import static java.lang.annotation.ElementType.*;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+/**
+ * Denotes that a parameter, field or method return value can be null.
+ * @paramDoc This value may be {@code null}.
+ * @returnDoc This value may be {@code null}.
+ * @hide
+ */
+@SuppressWarnings({"WeakerAccess", "JavaDoc"})
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD, TYPE_USE})
+public @interface Nullable {
+}
+                """
+)
+
+val supportNonNullSource: TestFile = java(
+    """
+package android.support.annotation;
+import java.lang.annotation.*;
+import static java.lang.annotation.ElementType.*;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+@SuppressWarnings("WeakerAccess")
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD, TYPE_USE})
+public @interface NonNull {
+}
+
+"""
+)
+
+val supportNullableSource: TestFile = java(
+    """
+package android.support.annotation;
+import java.lang.annotation.*;
+import static java.lang.annotation.ElementType.*;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+@SuppressWarnings("WeakerAccess")
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD, TYPE_USE})
+public @interface Nullable {
+}
+                """
+)
+
+val supportParameterName: TestFile = java(
+    """
+package android.support.annotation;
+import java.lang.annotation.*;
+import static java.lang.annotation.ElementType.*;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+@SuppressWarnings("WeakerAccess")
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface ParameterName {
+    String value();
+}
+
+"""
+)
+
+val uiThreadSource: TestFile = java(
+    """
+package android.support.annotation;
+import java.lang.annotation.*;
+import static java.lang.annotation.ElementType.*;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+/**
+ * Denotes that the annotated method or constructor should only be called on the
+ * UI thread. If the annotated element is a class, then all methods in the class
+ * should be called on the UI thread.
+ * @memberDoc This method must be called on the thread that originally created
+ *            this UI element. This is typically the main thread of your app.
+ * @classDoc Methods in this class must be called on the thread that originally created
+ *            this UI element, unless otherwise noted. This is typically the
+ *            main thread of your app. * @hide
+ */
+@SuppressWarnings({"WeakerAccess", "JavaDoc"})
+@Retention(SOURCE)
+@Target({METHOD,CONSTRUCTOR,TYPE,PARAMETER})
+public @interface UiThread {
+}
+                """
+)
+
+val workerThreadSource: TestFile = java(
+    """
+package android.support.annotation;
+import java.lang.annotation.*;
+import static java.lang.annotation.ElementType.*;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+/**
+ * @memberDoc This method may take several seconds to complete, so it should
+ *            only be called from a worker thread.
+ * @classDoc Methods in this class may take several seconds to complete, so it should
+ *            only be called from a worker thread unless otherwise noted.
+ * @hide
+ */
+@SuppressWarnings({"WeakerAccess", "JavaDoc"})
+@Retention(SOURCE)
+@Target({METHOD,CONSTRUCTOR,TYPE,PARAMETER})
+public @interface WorkerThread {
+}
+                """
+)
+
+val suppressLintSource: TestFile = java(
+    """
+package android.annotation;
+
+import static java.lang.annotation.ElementType.*;
+import java.lang.annotation.*;
+@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
+@Retention(RetentionPolicy.CLASS)
+public @interface SuppressLint {
+    String[] value();
+}
+                """
+)
+
+val systemServiceSource: TestFile = java(
+    """
+package android.annotation;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+import java.lang.annotation.*;
+@Retention(SOURCE)
+@Target(TYPE)
+public @interface SystemService {
+    String value();
+}
+                """
+)
+
+val systemApiSource: TestFile = java(
+    """
+package android.annotation;
+import static java.lang.annotation.ElementType.*;
+import java.lang.annotation.*;
+@Target({TYPE, FIELD, METHOD, CONSTRUCTOR, ANNOTATION_TYPE, PACKAGE})
+@Retention(RetentionPolicy.SOURCE)
+public @interface SystemApi {
+}
+"""
+)
+
diff --git a/src/test/java/com/android/tools/metalava/ExtractAnnotationsTest.kt b/src/test/java/com/android/tools/metalava/ExtractAnnotationsTest.kt
new file mode 100644
index 0000000..517abd7
--- /dev/null
+++ b/src/test/java/com/android/tools/metalava/ExtractAnnotationsTest.kt
@@ -0,0 +1,432 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava
+
+import com.android.utils.SdkUtils.fileToUrlString
+import com.google.common.base.Charsets
+import com.google.common.io.ByteStreams
+import com.google.common.io.Closeables
+import com.google.common.io.Files
+import org.intellij.lang.annotations.Language
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import java.io.File
+import java.net.URL
+
+@SuppressWarnings("ALL") // Sample code
+class ExtractAnnotationsTest : DriverTest() {
+
+    @Test
+    fun `Include class retention`() {
+        val androidJar = getPlatformFile("android.jar")
+
+        val project = createProject(
+            packageTest,
+            genericTest,
+            intDefTest,
+            permissionsTest,
+            manifest,
+            intDefAnnotation,
+            intRangeAnnotation,
+            permissionAnnotation,
+            nullableAnnotation
+        )
+
+        val output = temporaryFolder.newFile("annotations.zip")
+
+        runDriver(
+            "--sources",
+            File(project, "src").path,
+            "--classpath",
+            androidJar.path,
+            "--extract-annotations",
+            output.path
+        )
+
+        // Check extracted annotations
+        checkPackageXml(
+            "test.pkg", output, """<?xml version="1.0" encoding="UTF-8"?>
+<root>
+  <item name="test.pkg">
+    <annotation name="android.support.annotation.IntRange">
+      <val name="from" val="20" />
+    </annotation>
+  </item>
+  <item name="test.pkg.IntDefTest void setFlags(java.lang.Object, int) 1">
+    <annotation name="android.support.annotation.IntDef">
+      <val name="value" val="{test.pkg.IntDefTest.STYLE_NORMAL, test.pkg.IntDefTest.STYLE_NO_TITLE, test.pkg.IntDefTest.STYLE_NO_FRAME, test.pkg.IntDefTest.STYLE_NO_INPUT, 3, 4}" />
+      <val name="flag" val="true" />
+    </annotation>
+  </item>
+  <item name="test.pkg.IntDefTest void setStyle(int, int) 0">
+    <annotation name="android.support.annotation.IntDef">
+      <val name="value" val="{test.pkg.IntDefTest.STYLE_NORMAL, test.pkg.IntDefTest.STYLE_NO_TITLE, test.pkg.IntDefTest.STYLE_NO_FRAME, test.pkg.IntDefTest.STYLE_NO_INPUT}" />
+    </annotation>
+    <annotation name="android.support.annotation.IntRange">
+      <val name="from" val="20" />
+    </annotation>
+  </item>
+  <item name="test.pkg.IntDefTest.Inner void setInner(int) 0">
+    <annotation name="android.support.annotation.IntDef">
+      <val name="value" val="{test.pkg.IntDefTest.STYLE_NORMAL, test.pkg.IntDefTest.STYLE_NO_TITLE, test.pkg.IntDefTest.STYLE_NO_FRAME, test.pkg.IntDefTest.STYLE_NO_INPUT, 3, 4}" />
+      <val name="flag" val="true" />
+    </annotation>
+  </item>
+  <item name="test.pkg.MyEnhancedList">
+    <annotation name="android.support.annotation.IntRange">
+      <val name="from" val="0" />
+    </annotation>
+  </item>
+  <item name="test.pkg.MyEnhancedList E getReversed(java.util.List&lt;java.lang.String&gt;, java.util.Comparator&lt;? super E&gt;)">
+    <annotation name="android.support.annotation.IntRange">
+      <val name="from" val="10" />
+    </annotation>
+  </item>
+  <item name="test.pkg.MyEnhancedList java.lang.String getPrefix()">
+    <annotation name="android.support.annotation.Nullable" />
+  </item>
+  <item name="test.pkg.PermissionsTest CONTENT_URI">
+    <annotation name="android.support.annotation.RequiresPermission.Read">
+      <val name="value" val="&quot;android.permission.MY_READ_PERMISSION_STRING&quot;" />
+    </annotation>
+    <annotation name="android.support.annotation.RequiresPermission.Write">
+      <val name="value" val="&quot;android.permission.MY_WRITE_PERMISSION_STRING&quot;" />
+    </annotation>
+  </item>
+  <item name="test.pkg.PermissionsTest void myMethod()">
+    <annotation name="android.support.annotation.RequiresPermission">
+      <val name="value" val="&quot;android.permission.MY_PERMISSION_STRING&quot;" />
+    </annotation>
+  </item>
+  <item name="test.pkg.PermissionsTest void myMethod2()">
+    <annotation name="android.support.annotation.RequiresPermission">
+      <val name="anyOf" val="{&quot;android.permission.MY_PERMISSION_STRING&quot;, &quot;android.permission.MY_PERMISSION_STRING2&quot;}" />
+    </annotation>
+  </item>
+</root>
+
+"""
+        )
+    }
+
+    @Test
+    fun `Skip class retention`() {
+        val androidJar = getPlatformFile("android.jar")
+
+        val project = createProject(
+            intDefTest,
+            permissionsTest,
+            manifest,
+            intDefAnnotation,
+            intRangeAnnotation,
+            permissionAnnotation
+        )
+
+        val output = temporaryFolder.newFile("annotations.zip")
+
+        runDriver(
+            "--sources",
+            File(project, "src").path,
+            "--classpath",
+            androidJar.path,
+            "--skip-class-retention",
+            "--extract-annotations",
+            output.path
+        )
+
+        // Check external annotations
+        checkPackageXml(
+            "test.pkg", output,
+            """<?xml version="1.0" encoding="UTF-8"?>
+<root>
+  <item name="test.pkg.IntDefTest void setFlags(java.lang.Object, int) 1">
+    <annotation name="android.support.annotation.IntDef">
+      <val name="value" val="{test.pkg.IntDefTest.STYLE_NORMAL, test.pkg.IntDefTest.STYLE_NO_TITLE, test.pkg.IntDefTest.STYLE_NO_FRAME, test.pkg.IntDefTest.STYLE_NO_INPUT, 3, 4}" />
+      <val name="flag" val="true" />
+    </annotation>
+  </item>
+  <item name="test.pkg.IntDefTest void setStyle(int, int) 0">
+    <annotation name="android.support.annotation.IntDef">
+      <val name="value" val="{test.pkg.IntDefTest.STYLE_NORMAL, test.pkg.IntDefTest.STYLE_NO_TITLE, test.pkg.IntDefTest.STYLE_NO_FRAME, test.pkg.IntDefTest.STYLE_NO_INPUT}" />
+    </annotation>
+    <annotation name="android.support.annotation.IntRange">
+      <val name="from" val="20" />
+    </annotation>
+  </item>
+  <item name="test.pkg.IntDefTest.Inner void setInner(int) 0">
+    <annotation name="android.support.annotation.IntDef">
+      <val name="value" val="{test.pkg.IntDefTest.STYLE_NORMAL, test.pkg.IntDefTest.STYLE_NO_TITLE, test.pkg.IntDefTest.STYLE_NO_FRAME, test.pkg.IntDefTest.STYLE_NO_INPUT, 3, 4}" />
+      <val name="flag" val="true" />
+    </annotation>
+  </item>
+</root>
+
+"""
+        )
+    }
+
+    @Test
+    fun `Test writing jar recipe file`() {
+        val androidJar = getPlatformFile("android.jar")
+
+        val project = createProject(
+            intDefTest,
+            permissionsTest,
+            manifest,
+            intDefAnnotation,
+            intRangeAnnotation,
+            permissionAnnotation
+        )
+
+        val output = temporaryFolder.newFile("annotations.zip")
+        val typedefFile = temporaryFolder.newFile("typedefs.txt")
+
+        runDriver(
+            "--sources",
+            File(project, "src").path,
+            "--classpath",
+            androidJar.path,
+
+            "--extract-annotations",
+            output.path,
+            "--typedef-file",
+            typedefFile.path
+        )
+
+        // Check recipe
+        assertEquals(
+            """D test/pkg/IntDefTest${"$"}DialogFlags
+D test/pkg/IntDefTest${"$"}DialogStyle
+""",
+            Files.asCharSource(typedefFile, Charsets.UTF_8).read()
+        )
+    }
+
+    @SuppressWarnings("all") // sample code
+    private val intDefAnnotation = java(
+        """
+                package android.support.annotation;
+                import java.lang.annotation.Retention;
+                import java.lang.annotation.RetentionPolicy;
+                import java.lang.annotation.Target;
+                import static java.lang.annotation.ElementType.*;
+                import static java.lang.annotation.RetentionPolicy.SOURCE;
+                @Retention(SOURCE)
+                @Target({ANNOTATION_TYPE})
+                public @interface IntDef {
+                    long[] value() default {};
+                    boolean flag() default false;
+                }
+                """
+    ).indented()
+
+    @SuppressWarnings("all") // sample code
+    private val intRangeAnnotation = java(
+        """
+                package android.support.annotation;
+
+                import java.lang.annotation.Retention;
+                import java.lang.annotation.Target;
+
+                import static java.lang.annotation.ElementType.*;
+                import static java.lang.annotation.RetentionPolicy.CLASS;
+
+                @Retention(CLASS)
+                @Target({CONSTRUCTOR,METHOD,PARAMETER,FIELD,LOCAL_VARIABLE,ANNOTATION_TYPE,PACKAGE})
+                public @interface IntRange {
+                    long from() default Long.MIN_VALUE;
+                    long to() default Long.MAX_VALUE;
+                }
+                """
+    ).indented()
+
+    @SuppressWarnings("all") // sample code
+    private val permissionAnnotation = java(
+        """
+                package android.support.annotation;
+                import java.lang.annotation.Retention;
+                import java.lang.annotation.RetentionPolicy;
+                import java.lang.annotation.Target;
+                import static java.lang.annotation.ElementType.*;
+                import static java.lang.annotation.RetentionPolicy.*;
+                @Retention(CLASS)
+                @Target({ANNOTATION_TYPE,METHOD,CONSTRUCTOR,FIELD})
+                public @interface RequiresPermission {
+                    String value() default "";
+                    String[] allOf() default {};
+                    String[] anyOf() default {};
+                    boolean conditional() default false;
+                    @Target(FIELD)
+                    @interface Read {
+                        RequiresPermission value();
+                    }
+                    @Target(FIELD)
+                    @interface Write {
+                        RequiresPermission value();
+                    }
+                }"""
+    ).indented()
+
+    @SuppressWarnings("all") // sample code
+    private val nullableAnnotation = java(
+        """
+                package android.support.annotation;
+                import java.lang.annotation.*;
+                import static java.lang.annotation.ElementType.*;
+                import static java.lang.annotation.RetentionPolicy.*;
+                @Retention(CLASS)
+                @Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE, ANNOTATION_TYPE, PACKAGE})
+                public @interface Nullable {
+                }"""
+    ).indented()
+
+    @SuppressWarnings("all") // sample code
+    private val packageTest = java(
+        """
+                @IntRange(from = 20)
+                package test.pkg;
+
+                import android.support.annotation.IntRange;"""
+    ).indented()
+
+    @SuppressWarnings("all") // sample code
+    private val genericTest = java(
+        """
+                package test.pkg;
+
+                import android.support.annotation.IntRange;
+                import android.support.annotation.Nullable;
+
+                import java.util.Comparator;
+                import java.util.List;
+
+                @IntRange(from = 0)
+                public interface MyEnhancedList<E> extends List<E> {
+                    @IntRange(from = 10)
+                    E getReversed(List<String> filter, Comparator<? super E> comparator);
+                    @Nullable String getPrefix();
+                }
+                """
+    )
+
+    @SuppressWarnings("all") // sample code
+    private val intDefTest = java(
+        """
+                package test.pkg;
+
+                import android.support.annotation.IntDef;
+                import android.support.annotation.IntRange;
+                import android.support.annotation.Keep;
+
+                import java.lang.annotation.Retention;
+                import java.lang.annotation.RetentionPolicy;
+
+                @SuppressWarnings({"UnusedDeclaration", "WeakerAccess"})
+                public class IntDefTest {
+                    @IntDef({STYLE_NORMAL, STYLE_NO_TITLE, STYLE_NO_FRAME, STYLE_NO_INPUT})
+                    @IntRange(from = 20)
+                    @Retention(RetentionPolicy.SOURCE)
+                    private @interface DialogStyle {}
+
+                    public static final int STYLE_NORMAL = 0;
+                    public static final int STYLE_NO_TITLE = 1;
+                    public static final int STYLE_NO_FRAME = 2;
+                    public static final int STYLE_NO_INPUT = 3;
+                    public static final int UNRELATED = 3;
+
+                    public void setStyle(@DialogStyle int style, int theme) {
+                    }
+
+                    @Keep    public void testIntDef(int arg) {
+                    }
+                    @IntDef(value = {STYLE_NORMAL, STYLE_NO_TITLE, STYLE_NO_FRAME, STYLE_NO_INPUT, 3, 3 + 1}, flag=true)
+                    @Retention(RetentionPolicy.SOURCE)
+                    private @interface DialogFlags {}
+
+                    public void setFlags(Object first, @DialogFlags int flags) {
+                    }
+
+                    public static final String TYPE_1 = "type1";
+                    public static final String TYPE_2 = "type2";
+                    public static final String UNRELATED_TYPE = "other";
+
+                    public static class Inner {
+                        public void setInner(@DialogFlags int flags) {
+                        }
+                    }
+                }"""
+    ).indented()
+
+    @SuppressWarnings("all") // sample code
+    private val permissionsTest = java(
+        """
+                    package test.pkg;
+
+                    import android.support.annotation.RequiresPermission;
+
+                    public class PermissionsTest {
+                        @RequiresPermission(Manifest.permission.MY_PERMISSION)
+                        public void myMethod() {
+                        }
+                        @RequiresPermission(anyOf={Manifest.permission.MY_PERMISSION,Manifest.permission.MY_PERMISSION2})
+                        public void myMethod2() {
+                        }
+
+
+                        @RequiresPermission.Read(@RequiresPermission(Manifest.permission.MY_READ_PERMISSION))
+                        @RequiresPermission.Write(@RequiresPermission(Manifest.permission.MY_WRITE_PERMISSION))
+                        public static final String CONTENT_URI = "";
+                    }
+                    """
+    )
+
+    @SuppressWarnings("all") // sample code
+    private val manifest = java(
+        """
+                    package test.pkg;
+
+                    public class Manifest {
+                        public static final class permission {
+                            public static final String MY_PERMISSION = "android.permission.MY_PERMISSION_STRING";
+                            public static final String MY_PERMISSION2 = "android.permission.MY_PERMISSION_STRING2";
+                            public static final String MY_READ_PERMISSION = "android.permission.MY_READ_PERMISSION_STRING";
+                            public static final String MY_WRITE_PERMISSION = "android.permission.MY_WRITE_PERMISSION_STRING";
+                        }
+                    }
+                    """
+    ).indented()
+
+    private fun checkPackageXml(pkg: String, output: File, @Language("XML") expected: String) {
+        assertNotNull(output)
+        assertTrue(output.exists())
+        val url = URL(
+            "jar:" + fileToUrlString(output) + "!/" + pkg.replace('.', '/') +
+                    "/annotations.xml"
+        )
+        val stream = url.openStream()
+        try {
+            val bytes = ByteStreams.toByteArray(stream)
+            assertNotNull(bytes)
+            val xml = String(bytes, Charsets.UTF_8).replace("\r\n", "\n")
+            assertEquals(expected, xml)
+        } finally {
+            Closeables.closeQuietly(stream)
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/metalava/KeepFileTest.kt b/src/test/java/com/android/tools/metalava/KeepFileTest.kt
new file mode 100644
index 0000000..00e17cd
--- /dev/null
+++ b/src/test/java/com/android/tools/metalava/KeepFileTest.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava
+
+import org.junit.Test
+
+class KeepFileTest : DriverTest() {
+    @Test
+    fun `Generate Keep file`() {
+        check(
+            checkDoclava1 = true,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    @SuppressWarnings("ALL")
+                    public interface MyInterface<T extends Object>
+                            extends MyBaseInterface {
+                    }
+                    """
+                ), java(
+                    """
+                    package a.b.c;
+                    @SuppressWarnings("ALL")
+                    public interface MyStream<T, S extends MyStream<T, S>> extends java.lang.AutoCloseable {
+                    }
+                    """
+                ), java(
+                    """
+                    package test.pkg;
+                    @SuppressWarnings("ALL")
+                    public interface MyInterface2<T extends Number>
+                            extends MyBaseInterface {
+                        class TtsSpan<C extends MyInterface<?>> { }
+                        abstract class Range<T extends Comparable<? super T>> {
+                            protected String myString;
+                        }
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg;
+                    public interface MyBaseInterface {
+                        void fun(int a, String b);
+                    }
+                    """
+                )
+            ),
+            proguard = """
+                -keep class a.b.c.MyStream {
+                }
+                -keep class test.pkg.MyBaseInterface {
+                    public abstract void fun(int, java.lang.String);
+                }
+                -keep class test.pkg.MyInterface {
+                }
+                -keep class test.pkg.MyInterface2 {
+                }
+                -keep class test.pkg.MyInterface2${"$"}Range {
+                    <init>();
+                    protected java.lang.String myString;
+                }
+                -keep class test.pkg.MyInterface2${"$"}TtsSpan {
+                    <init>();
+                }
+                """
+        )
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/metalava/NullnessMigrationTest.kt b/src/test/java/com/android/tools/metalava/NullnessMigrationTest.kt
new file mode 100644
index 0000000..fcf9ecc
--- /dev/null
+++ b/src/test/java/com/android/tools/metalava/NullnessMigrationTest.kt
@@ -0,0 +1,291 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava
+
+import org.junit.Test
+
+class NullnessMigrationTest : DriverTest() {
+    @Test
+    fun `Test Kotlin-style null signatures`() {
+        check(
+            compatibilityMode = false,
+            signatureSource = """
+                package test.pkg {
+                  public class MyTest {
+                    ctor public MyTest();
+                    method public Double convert0(Float);
+                    method @Nullable public Double convert1(@NonNull Float);
+                    method @Nullable public Double convert2(@NonNull Float);
+                    method @Nullable public Double convert3(@NonNull Float);
+                    method @Nullable public Double convert4(@NonNull Float);
+                  }
+                }
+                """,
+            api = """
+                package test.pkg {
+                  public class MyTest {
+                    ctor public MyTest();
+                    method public Double! convert0(Float!);
+                    method public Double? convert1(Float);
+                    method public Double? convert2(Float);
+                    method public Double? convert3(Float);
+                    method public Double? convert4(Float);
+                  }
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Method which is now marked null should be marked as newly migrated null`() {
+        check(
+            migrateNulls = true,
+            outputKotlinStyleNulls = false,
+            compatibilityMode = false,
+            signatureSource = """
+                package test.pkg {
+                  public abstract class MyTest {
+                    method @Nullable public Double convert1(Float);
+                  }
+                }
+                """,
+            previousApi = """
+                package test.pkg {
+                  public abstract class MyTest {
+                    method public Double convert1(Float);
+                  }
+                }
+                """,
+            api = """
+                package test.pkg {
+                  public abstract class MyTest {
+                    method @NewlyNullable public Double convert1(Float);
+                  }
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Parameter which is now marked null should be marked as newly migrated null`() {
+        check(
+            migrateNulls = true,
+            outputKotlinStyleNulls = false,
+            compatibilityMode = false,
+            signatureSource = """
+                package test.pkg {
+                  public abstract class MyTest {
+                    method public Double convert1(@NonNull Float);
+                  }
+                }
+                """,
+            previousApi = """
+                package test.pkg {
+                  public abstract class MyTest {
+                    method public Double convert1(Float);
+                  }
+                }
+                """,
+            api = """
+                package test.pkg {
+                  public abstract class MyTest {
+                    method public Double convert1(@NewlyNonNull Float);
+                  }
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Comprehensive check of migration`() {
+        check(
+            migrateNulls = true,
+            outputKotlinStyleNulls = false,
+            compatibilityMode = false,
+            signatureSource = """
+                package test.pkg {
+                  public class MyTest {
+                    ctor public MyTest();
+                    method public Double convert0(Float);
+                    method @Nullable public Double convert1(@NonNull Float);
+                    method @Nullable public Double convert2(@NonNull Float);
+                    method @Nullable public Double convert3(@NonNull Float);
+                    method @Nullable public Double convert4(@NonNull Float);
+                  }
+                }
+                """,
+            previousApi = """
+                package test.pkg {
+                  public class MyTest {
+                    ctor public MyTest();
+                    method public Double convert0(Float);
+                    method public Double convert1(Float);
+                    method @NewlyNullable public Double convert2(@NewlyNonNull Float);
+                    method @RecentlyNullable public Double convert3(@RecentlyNonNull Float);
+                    method @Nullable public Double convert4(@NonNull Float);
+                  }
+                }
+                """,
+            api = """
+                package test.pkg {
+                  public class MyTest {
+                    ctor public MyTest();
+                    method public Double convert0(Float);
+                    method @NewlyNullable public Double convert1(@NewlyNonNull Float);
+                    method @RecentlyNullable public Double convert2(@RecentlyNonNull Float);
+                    method @Nullable public Double convert3(@NonNull Float);
+                    method @Nullable public Double convert4(@NonNull Float);
+                  }
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Comprehensive check of migration, Kotlin-style output`() {
+        check(
+            migrateNulls = true,
+            outputKotlinStyleNulls = true,
+            compatibilityMode = false,
+            signatureSource = """
+                package test.pkg {
+                  public class MyTest {
+                    ctor public MyTest();
+                    method public Double convert0(Float);
+                    method @Nullable public Double convert1(@NonNull Float);
+                    method @Nullable public Double convert2(@NonNull Float);
+                    method @Nullable public Double convert3(@NonNull Float);
+                    method @Nullable public Double convert4(@NonNull Float);
+                  }
+                }
+                """,
+            previousApi = """
+                package test.pkg {
+                  public class MyTest {
+                    ctor public MyTest();
+                    method public Double convert0(Float);
+                    method public Double convert1(Float);
+                    method @NewlyNullable public Double convert2(@NewlyNonNull Float);
+                    method @RecentlyNullable public Double convert3(@RecentlyNonNull Float);
+                    method @Nullable public Double convert4(@NonNull Float);
+                  }
+                }
+                """,
+            api = """
+                package test.pkg {
+                  public class MyTest {
+                    ctor public MyTest();
+                    method public Double! convert0(Float!);
+                    method public Double? convert1(Float);
+                    method public Double? convert2(Float);
+                    method public Double? convert3(Float);
+                    method public Double? convert4(Float);
+                  }
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Convert libcore nullness annotations to support`() {
+        check(
+            outputKotlinStyleNulls = false,
+            compatibilityMode = false,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    public class Test {
+                        public @libcore.util.NonNull Object compute() {
+                            return 5;
+                        }
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package libcore.util;
+                    import static java.lang.annotation.ElementType.TYPE_USE;
+                    import static java.lang.annotation.ElementType.TYPE_PARAMETER;
+                    import static java.lang.annotation.RetentionPolicy.SOURCE;
+                    import java.lang.annotation.Documented;
+                    import java.lang.annotation.Retention;
+                    @Documented
+                    @Retention(SOURCE)
+                    @Target({TYPE_USE})
+                    public @interface NonNull {
+                       int from() default Integer.MIN_VALUE;
+                       int to() default Integer.MAX_VALUE;
+                    }
+                    """
+                )
+            ),
+            api = """
+                    package libcore.util {
+                      public @interface NonNull {
+                        method public abstract int from();
+                        method public abstract int to();
+                      }
+                    }
+                    package test.pkg {
+                      public class Test {
+                        ctor public Test();
+                        method @NonNull public Object compute();
+                      }
+                    }
+                """
+        )
+    }
+
+    @Test
+    fun `Check type use annotations`() {
+        check(
+            outputKotlinStyleNulls = false,
+            compatibilityMode = false,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    import android.support.annotation.Nullable;
+                    import java.util.List;
+                    public class Test {
+                        public @Nullable Integer compute1(@Nullable java.util.List<@Nullable String> list) {
+                            return 5;
+                        }
+                        public @Nullable Integer compute2(@Nullable java.util.List<@Nullable List<?>> list) {
+                            return 5;
+                        }
+                        // TODO arrays
+                    }
+                    """
+                ),
+                supportNonNullSource,
+                supportNullableSource
+            ),
+            extraArguments = arrayOf("--hide-package", "android.support.annotation"),
+            api = """
+                package test.pkg {
+                  public class Test {
+                    ctor public Test();
+                    method @Nullable public Integer compute1(@Nullable java.util.List<@Nullable String>);
+                    method @Nullable public Integer compute2(@Nullable java.util.List<@Nullable java.util.List<?>>);
+                  }
+                }
+                """
+        )
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/metalava/OptionsTest.kt b/src/test/java/com/android/tools/metalava/OptionsTest.kt
new file mode 100644
index 0000000..e9f35ca
--- /dev/null
+++ b/src/test/java/com/android/tools/metalava/OptionsTest.kt
@@ -0,0 +1,207 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava
+
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import java.io.PrintWriter
+import java.io.StringWriter
+
+@Suppress("PrivatePropertyName")
+class OptionsTest : DriverTest() {
+    private val DESCRIPTION = """
+$PROGRAM_NAME extracts metadata from source code to generate artifacts such as the signature
+files, the SDK stub files, external annotations etc.
+""".trimIndent()
+
+    private val FLAGS = """
+Usage: $PROGRAM_NAME <flags>
+
+General:
+--help                                 This message.
+--quiet                                Only include vital output
+--verbose                              Include extra diagnostic output
+--color                                Attempt to colorize the output (defaults to true if
+                                       ${"$"}TERM is xterm)
+--no-color                             Do not attempt to colorize the output
+
+API sources:
+--source-files <files>                 A comma separated list of source files to be
+                                       parsed. Can also be @ followed by a path to a text
+                                       file containing paths to the full set of files to
+                                       parse.
+--source-path <paths>                  One or more directories (separated by `:`)
+                                       containing source files (within a package
+                                       hierarchy)
+--classpath <paths>                    One or more directories or jars (separated by `:`)
+                                       containing classes that should be on the classpath
+                                       when parsing the source files
+--merge-annotations <file>             An external annotations file (using IntelliJ's
+                                       external annotations database format) to merge and
+                                       overlay the sources
+--input-api-jar <file>                 A .jar file to read APIs from directly
+--manifest <file>                      A manifest file, used to for check permissions to
+                                       cross check APIs
+--hide-package <package>               Remove the given packages from the API even if they
+                                       have not been marked with @hide
+--show-annotation <annotation class>   Include the given annotation in the API analysis
+--show-unannotated                     Include un-annotated public APIs in the signature
+                                       file as well
+
+Extracting Signature Files:
+--api <file>                           Generate a signature descriptor file
+--private-api <file>                   Generate a signature descriptor file listing the
+                                       exact private APIs
+--private-dex-api <file>               Generate a DEX signature descriptor file listing
+                                       the exact private APIs
+--removed-api <file>                   Generate a signature descriptor file for APIs that
+                                       have been removed
+--output-kotlin-nulls[=yes|no]         Controls whether nullness annotations should be
+                                       formatted as in Kotlin (with "?" for nullable
+                                       types, "" for non nullable types, and "!" for
+                                       unknown. The default is yes.
+--compatible-output=[yes|no]           Controls whether to keep signature files compatible
+                                       with the historical format (with its various
+                                       quirks) or to generate the new format (which will
+                                       also include annotations that are part of the API,
+                                       etc.)
+--omit-common-packages[=yes|no]        Skip common package prefixes like java.lang.* and
+                                       kotlin.* in signature files, along with packages
+                                       for well known annotations like @Nullable and
+                                       @NonNull.
+--proguard <file>                      Write a ProGuard keep file for the API
+
+Generating Stubs:
+--stubs <dir>                          Generate stub source files for the API
+--exclude-annotations                  Exclude annotations such as @Nullable from the stub
+                                       files
+--write-stubs-source-list <file>       Write the list of generated stub files into the
+                                       given source list file
+
+Diffs and Checks:
+--previous-api <signature file>        A signature file for the previous version of this
+                                       API to apply diffs with
+--input-kotlin-nulls[=yes|no]          Whether the signature file being read should be
+                                       interpreted as having encoded its types using
+                                       Kotlin style types: a suffix of "?" for nullable
+                                       types, no suffix for non nullable types, and "!"
+                                       for unknown. The default is no.
+--check-compatibility                  Check compatibility with the previous API
+--migrate-nullness                     Compare nullness information with the previous API
+                                       and mark newly annotated APIs as under migration.
+--warnings-as-errors                   Promote all warnings to errors
+--lints-as-errors                      Promote all API lint warnings to errors
+--error <id>                           Report issues of the given id as errors
+--warning <id>                         Report issues of the given id as warnings
+--lint <id>                            Report issues of the given id as having
+                                       lint-severity
+--hide <id>                            Hide/skip issues of the given id
+
+Statistics:
+--annotation-coverage-stats            Whether metalava should emit coverage statistics
+                                       for annotations, listing the percentage of the API
+                                       that has been annotated with nullness information.
+--annotation-coverage-of <paths>       One or more jars (separated by `:`) containing
+                                       existing apps that we want to measure annotation
+                                       coverage statistics for. The set of API usages in
+                                       those apps are counted up and the most frequently
+                                       used APIs that are missing annotation metadata are
+                                       listed in descending order.
+--skip-java-in-coverage-report         In the coverage annotation report, skip java.** and
+                                       kotlin.** to narrow the focus down to the Android
+                                       framework APIs.
+
+Extracting Annotations:
+--extract-annotations <zipfile>        Extracts annotations from the source files and
+                                       writes them into the given zip file
+--api-filter <file>                    Applies the given signature file as a filter (which
+                                       means no classes,methods or fields not found in the
+                                       filter will be included.)
+--hide-filtered                        Omit listing APIs that were skipped because of the
+                                       --api-filter
+--skip-class-retention                 Do not extract annotations that have class file
+                                       retention
+--rmtypedefs                           Delete all the typedef .class files
+--typedef-file <file>                  Writes an typedef annotation class names into the
+                                       given file
+
+Injecting API Levels:
+--apply-api-levels <api-versions.xml>  Reads an XML file containing API level descriptions
+                                       and merges the information into the documentation
+
+Extracting API Levels:
+--generate-api-levels <xmlfile>        Reads android.jar SDK files and generates an XML
+                                       file recording the API level for each class, method
+                                       and field
+--android-jar-pattern <pattern>        Patterns to use to locate Android JAR files. The
+                                       default is
+                                       ${"$"}ANDROID_HOME/platforms/android-%/android.jar.
+--current-version                      Sets the current API level of the current source
+                                       code
+--current-codename                     Sets the code name for the current source code
+--current-jar                          Points to the current API jar, if any
+
+""".trimIndent()
+
+    @Test
+    fun `Test invalid arguments`() {
+        val args = listOf("--no-color", "--blah-blah-blah")
+
+        val stdout = StringWriter()
+        val stderr = StringWriter()
+        com.android.tools.metalava.run(
+            args = args.toTypedArray(),
+            stdout = PrintWriter(stdout),
+            stderr = PrintWriter(stderr)
+        )
+        assertEquals(BANNER + "\n\n", stdout.toString())
+        assertEquals(
+            """
+
+Invalid argument --blah-blah-blah
+
+$FLAGS
+
+""".trimIndent(), stderr.toString()
+        )
+    }
+
+    @Test
+    fun `Test help`() {
+        val args = listOf("--no-color", "--help")
+
+        val stdout = StringWriter()
+        val stderr = StringWriter()
+        com.android.tools.metalava.run(
+            args = args.toTypedArray(),
+            stdout = PrintWriter(stdout),
+            stderr = PrintWriter(stderr)
+        )
+        assertEquals("", stderr.toString())
+        assertEquals(
+            """
+$BANNER
+
+
+$DESCRIPTION
+
+$FLAGS
+
+""".trimIndent(), stdout.toString()
+        )
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/metalava/PackageFilterTest.kt b/src/test/java/com/android/tools/metalava/PackageFilterTest.kt
new file mode 100644
index 0000000..e092d78
--- /dev/null
+++ b/src/test/java/com/android/tools/metalava/PackageFilterTest.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava
+
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+class PackageFilterTest {
+    @Test
+    fun test() {
+        val filter = PackageFilter(mutableListOf("foo.bar", "bar.baz"))
+        assertThat(filter.matches("foo.bar")).isTrue()
+        assertThat(filter.matches("bar.baz")).isTrue()
+        assertThat(filter.matches("foo.bar.baz")).isTrue()
+        assertThat(filter.matches("foo.barf")).isFalse()
+        assertThat(filter.matches("foo")).isFalse()
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/metalava/ShowAnnotationTest.kt b/src/test/java/com/android/tools/metalava/ShowAnnotationTest.kt
new file mode 100644
index 0000000..37e3c68
--- /dev/null
+++ b/src/test/java/com/android/tools/metalava/ShowAnnotationTest.kt
@@ -0,0 +1,127 @@
+package com.android.tools.metalava
+
+import org.junit.Test
+
+/** Tests for the --show-annotation functionality */
+class ShowAnnotationTest : DriverTest() {
+
+    @Test
+    fun `Basic showAnnotation test`() {
+        check(
+            includeSystemApiAnnotations = true,
+            checkDoclava1 = true,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    import android.annotation.SystemApi;
+                    public class Foo {
+                        public void method1() { }
+
+                        /**
+                         * @hide Only for use by WebViewProvider implementations
+                         */
+                        @SystemApi
+                        public void method2() { }
+
+                        /**
+                         * @hide Always hidden
+                         */
+                        public void method3() { }
+
+                        @SystemApi
+                        public void method4() { }
+
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package foo.bar;
+                    public class Bar {
+                    }
+                """
+                ),
+                systemApiSource
+            ),
+
+            extraArguments = arrayOf(
+                "--hide-package", "android.annotation",
+                "--hide-package", "android.support.annotation"
+            ),
+
+            api = """
+                package test.pkg {
+                  public class Foo {
+                    method public void method2();
+                    method public void method4();
+                  }
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Basic showAnnotation with showUnannotated test`() {
+        check(
+            includeSystemApiAnnotations = true,
+            showUnannotated = true,
+            checkDoclava1 = true,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    import android.annotation.SystemApi;
+                    public class Foo {
+                        public void method1() { }
+
+                        /**
+                         * @hide Only for use by WebViewProvider implementations
+                         */
+                        @SystemApi
+                        public void method2() { }
+
+                        /**
+                         * @hide Always hidden
+                         */
+                        public void method3() { }
+
+                        @SystemApi
+                        public void method4() { }
+
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package foo.bar;
+                    public class Bar {
+                    }
+                """
+                ),
+                systemApiSource
+            ),
+
+            extraArguments = arrayOf(
+                "--hide-package", "android.annotation",
+                "--hide-package", "android.support.annotation"
+            ),
+
+            api = """
+                package foo.bar {
+                  public class Bar {
+                    ctor public Bar();
+                  }
+                }
+                package test.pkg {
+                  public class Foo {
+                    ctor public Foo();
+                    method public void method1();
+                    method public void method2();
+                    method public void method4();
+                  }
+                }
+                """
+        )
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/metalava/StubsTest.kt b/src/test/java/com/android/tools/metalava/StubsTest.kt
new file mode 100644
index 0000000..215bf43
--- /dev/null
+++ b/src/test/java/com/android/tools/metalava/StubsTest.kt
@@ -0,0 +1,2776 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+@file:Suppress("ALL")
+
+package com.android.tools.metalava
+
+import com.android.tools.lint.checks.infrastructure.TestFile
+import org.intellij.lang.annotations.Language
+import org.junit.Test
+
+@SuppressWarnings("ALL")
+class StubsTest : DriverTest() {
+    // TODO: test fields that need initialization
+    // TODO: test @DocOnly handling
+
+    private fun checkStubs(
+        @Language("JAVA") source: String,
+        compatibilityMode: Boolean = true,
+        warnings: String? = "",
+        checkDoclava1: Boolean = false,
+        api: String? = null,
+        extraArguments: Array<String> = emptyArray(),
+        vararg sourceFiles: TestFile
+    ) {
+        check(
+            *sourceFiles,
+            stubs = arrayOf(source),
+            compatibilityMode = compatibilityMode,
+            warnings = warnings,
+            checkDoclava1 = checkDoclava1,
+            checkCompilation = true,
+            api = api,
+            extraArguments = extraArguments
+        )
+    }
+
+    @Test
+    fun `Generate stubs for basic class`() {
+        checkStubs(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    /** This is the documentation for the class */
+                    @SuppressWarnings("ALL")
+                    public class Foo {
+                        private int hidden;
+
+                        /** My field doc */
+                        protected static final String field = "a\nb\n\"test\"";
+
+                        /**
+                         * Method documentation.
+                         * Maybe it spans
+                         * multiple lines.
+                         */
+                        protected static void onCreate(String parameter1) {
+                            // This is not in the stub
+                            System.out.println(parameter1);
+                        }
+
+                        static {
+                           System.out.println("Not included in stub");
+                        }
+                    }
+                    """
+                )
+            ),
+            source = """
+                package test.pkg;
+                /** This is the documentation for the class */
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public class Foo {
+                public Foo() { throw new RuntimeException("Stub!"); }
+                /**
+                 * Method documentation.
+                 * Maybe it spans
+                 * multiple lines.
+                 */
+                protected static void onCreate(java.lang.String parameter1) { throw new RuntimeException("Stub!"); }
+                /** My field doc */
+                protected static final java.lang.String field = "a\nb\n\"test\"";
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Generate stubs for generics`() {
+        // Basic interface with generics; makes sure <T extends Object> is written as just <T>
+        // Also include some more complex generics expressions to make sure they're serialized
+        // correctly (in particular, using fully qualified names instead of what appears in
+        // the source code.)
+        check(
+            checkDoclava1 = false,
+            checkCompilation = true,
+            sourceFiles =
+            *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    @SuppressWarnings("ALL")
+                    public interface MyInterface2<T extends Number>
+                            extends MyBaseInterface {
+                        class TtsSpan<C extends MyInterface<?>> { }
+                        abstract class Range<T extends Comparable<? super T>> { }
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg;
+                    @SuppressWarnings("ALL")
+                    public interface MyInterface<T extends Object>
+                            extends MyBaseInterface {
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg;
+                    public interface MyBaseInterface {
+                    }
+                    """
+                )
+            ),
+            warnings = "",
+            stubs = arrayOf(
+                """
+                package test.pkg;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public interface MyInterface2<T extends java.lang.Number> extends test.pkg.MyBaseInterface {
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public abstract static class Range<T extends java.lang.Comparable<? super T>> {
+                public Range() { throw new RuntimeException("Stub!"); }
+                }
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public static class TtsSpan<C extends test.pkg.MyInterface<?>> {
+                public TtsSpan() { throw new RuntimeException("Stub!"); }
+                }
+                }
+                """,
+                """
+                package test.pkg;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public interface MyInterface<T> extends test.pkg.MyBaseInterface {
+                }
+                """,
+                """
+                package test.pkg;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public interface MyBaseInterface {
+                }
+                """
+            )
+        )
+    }
+
+    @Test
+    fun `Generate stubs for class that should not get default constructor (has other constructors)`() {
+        // Class without explicit constructors (shouldn't insert default constructor)
+        checkStubs(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    public class Foo {
+                        public Foo(int i) {
+
+                        }
+                        public Foo(int i, int j) {
+                        }
+                    }
+                    """
+                )
+            ),
+            source = """
+                package test.pkg;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public class Foo {
+                public Foo(int i) { throw new RuntimeException("Stub!"); }
+                public Foo(int i, int j) { throw new RuntimeException("Stub!"); }
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Generate stubs for class that already has a private constructor`() {
+        // Class without private constructor; no default constructor should be inserted
+        checkStubs(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    public class Foo {
+                        private Foo() {
+                        }
+                    }
+                    """
+                )
+            ),
+            source = """
+                package test.pkg;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public class Foo {
+                Foo() { throw new RuntimeException("Stub!"); }
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Generate stubs for interface class`() {
+        // Interface: makes sure the right modifiers etc are shown (and that "package private" methods
+        // in the interface are taken to be public etc)
+        checkStubs(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    public interface Foo {
+                        void foo();
+                    }
+                    """
+                )
+            ),
+            source = """
+                package test.pkg;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public interface Foo {
+                public void foo();
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Generate stubs for enum`() {
+        // Interface: makes sure the right modifiers etc are shown (and that "package private" methods
+        // in the interface are taken to be public etc)
+        checkStubs(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    @SuppressWarnings("ALL")
+                    public enum Foo {
+                        A, B;
+                    }
+                    """
+                )
+            ),
+            source = """
+                package test.pkg;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public enum Foo {
+                A, B;
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Generate stubs for annotation type`() {
+        // Interface: makes sure the right modifiers etc are shown (and that "package private" methods
+        // in the interface are taken to be public etc)
+        checkStubs(
+            // For unknown reasons, doclava1 behaves differently here than when invoked on the
+            // whole platform
+            checkDoclava1 = false,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    import static java.lang.annotation.ElementType.*;
+                    import java.lang.annotation.*;
+                    @Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
+                    @Retention(RetentionPolicy.CLASS)
+                    public @interface Foo {
+                        String value();
+                    }
+                    """
+                )
+            ),
+            source = """
+                package test.pkg;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public @interface Foo {
+                public java.lang.String value();
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Generate stubs for class with superclass`() {
+        // Make sure superclass statement is correct; unlike signature files, inherited method from parent
+        // that has same signature should be included in the child
+        checkStubs(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    public class Foo extends Super {
+                        @Override public void base() { }
+                        public void child() { }
+                    }
+                    """
+                ), java(
+                    """
+                    package test.pkg;
+                    public class Super {
+                        public void base() { }
+                    }
+                    """
+                )
+            ),
+            source =
+            """
+                package test.pkg;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public class Foo extends test.pkg.Super {
+                public Foo() { throw new RuntimeException("Stub!"); }
+                public void base() { throw new RuntimeException("Stub!"); }
+                public void child() { throw new RuntimeException("Stub!"); }
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Generate stubs for fields with initial values`() {
+        checkStubs(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    @SuppressWarnings("ALL")
+                    public class Foo {
+                        private int hidden = 1;
+                        int hidden2 = 2;
+                        /** @hide */
+                        int hidden3 = 3;
+
+                        protected int field00; // No value
+                        public static final boolean field01 = true;
+                        public static final int field02 = 42;
+                        public static final long field03 = 42L;
+                        public static final short field04 = 5;
+                        public static final byte field05 = 5;
+                        public static final char field06 = 'c';
+                        public static final float field07 = 98.5f;
+                        public static final double field08 = 98.5;
+                        public static final String field09 = "String with \"escapes\" and \u00a9...";
+                        public static final double field10 = Double.NaN;
+                        public static final double field11 = Double.POSITIVE_INFINITY;
+
+                        public static final String GOOD_IRI_CHAR = "a-zA-Z0-9\u00a0-\ud7ff\uf900-\ufdcf\ufdf0-\uffef";
+                        public static final char HEX_INPUT = 61184;
+                    }
+                    """
+                )
+            ),
+            source = """
+                package test.pkg;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public class Foo {
+                public Foo() { throw new RuntimeException("Stub!"); }
+                public static final java.lang.String GOOD_IRI_CHAR = "a-zA-Z0-9\u00a0-\ud7ff\uf900-\ufdcf\ufdf0-\uffef";
+                public static final char HEX_INPUT = 61184; // 0xef00 '\uef00'
+                protected int field00;
+                public static final boolean field01 = true;
+                public static final int field02 = 42; // 0x2a
+                public static final long field03 = 42L; // 0x2aL
+                public static final short field04 = 5; // 0x5
+                public static final byte field05 = 5; // 0x5
+                public static final char field06 = 99; // 0x0063 'c'
+                public static final float field07 = 98.5f;
+                public static final double field08 = 98.5;
+                public static final java.lang.String field09 = "String with \"escapes\" and \u00a9...";
+                public static final double field10 = (0.0/0.0);
+                public static final double field11 = (1.0/0.0);
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Generate stubs for various modifier scenarios`() {
+        // Include as many modifiers as possible to see which ones are included
+        // in the signature files, and the expected sorting order.
+        // Note that the signature files treat "deprecated" as a fake modifier.
+        // Note also how the "protected" modifier on the interface method gets
+        // promoted to public.
+        checkStubs(
+            warnings = null,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+
+                    @SuppressWarnings("ALL")
+                    public abstract class Foo {
+                        @Deprecated private static final long field1 = 5;
+                        @Deprecated private static volatile long field2 = 5;
+                        @Deprecated public static strictfp final synchronized void method1() { }
+                        @Deprecated public static final synchronized native void method2();
+                        @Deprecated protected static final class Inner1 { }
+                        @Deprecated protected static abstract  class Inner2 { }
+                        @Deprecated protected interface Inner3 {
+                            protected default void method3() { }
+                            static void method4() { }
+                        }
+                    }
+                    """
+                )
+            ),
+
+            source = """
+                package test.pkg;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public abstract class Foo {
+                public Foo() { throw new RuntimeException("Stub!"); }
+                @Deprecated public static final synchronized strictfp void method1() { throw new RuntimeException("Stub!"); }
+                @Deprecated public static final synchronized native void method2();
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                @Deprecated protected static final class Inner1 {
+                protected Inner1() { throw new RuntimeException("Stub!"); }
+                }
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                @Deprecated protected abstract static class Inner2 {
+                protected Inner2() { throw new RuntimeException("Stub!"); }
+                }
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                @Deprecated protected static interface Inner3 {
+                public default void method3() { throw new RuntimeException("Stub!"); }
+                public static void method4() { throw new RuntimeException("Stub!"); }
+                }
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Generate stubs for class with abstract enum methods`() {
+        // As per https://bugs.openjdk.java.net/browse/JDK-6287639
+        // abstract methods in enums should not be listed as abstract,
+        // but doclava1 does, so replicate this.
+        // Also checks that we handle both enum fields and regular fields
+        // and that they are listed separately.
+
+        checkStubs(
+            checkDoclava1 = false, // Doclava1 does not generate compileable source for this
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+
+                    public enum FooBar {
+                        ABC {
+                            @Override
+                            protected void foo() { }
+                        }, DEF {
+                            @Override
+                            protected void foo() { }
+                        };
+
+                        protected abstract void foo();
+                        public static int field1 = 1;
+                        public int field2 = 2;
+                    }
+                    """
+                )
+            ),
+            source = """
+                package test.pkg;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public enum FooBar {
+                ABC, DEF;
+                protected void foo() { throw new RuntimeException("Stub!"); }
+                public static int field1 = 1; // 0x1
+                public int field2 = 2; // 0x2
+                }
+            """
+        )
+    }
+
+    @Test
+    fun `Check erasure in throws list`() {
+        // Makes sure that when we have a generic signature in the throws list we take
+        // the erasure instead (in compat mode); "Throwable" instead of "X" in the below
+        // test. Real world example: Optional.orElseThrow.
+        checkStubs(
+            compatibilityMode = true,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+
+                    import java.util.function.Supplier;
+
+                    @SuppressWarnings("RedundantThrows")
+                    public final class Test<T> {
+                        public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
+                            return null;
+                        }
+                    }
+                    """
+                )
+            ),
+            source = """
+                package test.pkg;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public final class Test<T> {
+                public Test() { throw new RuntimeException("Stub!"); }
+                public <X extends java.lang.Throwable> T orElseThrow(java.util.function.Supplier<? extends X> exceptionSupplier) throws java.lang.Throwable { throw new RuntimeException("Stub!"); }
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Generate stubs for additional generics scenarios`() {
+        // Some additional declarations where PSI default type handling diffs from doclava1
+        checkStubs(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+
+                    public abstract class Collections {
+                        public static <T extends java.lang.Object & java.lang.Comparable<? super T>> T max(java.util.Collection<? extends T> collection) {
+                            return null;
+                        }
+                        public abstract <T extends java.util.Collection<java.lang.String>> T addAllTo(T t);
+                        public final class Range<T extends java.lang.Comparable<? super T>> { }
+                    }
+                    """
+                )
+            ),
+
+            source = """
+                package test.pkg;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public abstract class Collections {
+                public Collections() { throw new RuntimeException("Stub!"); }
+                public static <T extends java.lang.Object & java.lang.Comparable<? super T>> T max(java.util.Collection<? extends T> collection) { throw new RuntimeException("Stub!"); }
+                public abstract <T extends java.util.Collection<java.lang.String>> T addAllTo(T t);
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public final class Range<T extends java.lang.Comparable<? super T>> {
+                public Range() { throw new RuntimeException("Stub!"); }
+                }
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Generate stubs for even more generics scenarios`() {
+        // Some additional declarations where PSI default type handling diffs from doclava1
+        checkStubs(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+
+                    import java.util.Set;
+
+                    @SuppressWarnings("ALL")
+                    public class MoreAsserts {
+                        public static void assertEquals(String arg0, Set<? extends Object> arg1, Set<? extends Object> arg2) { }
+                        public static void assertEquals(Set<? extends Object> arg1, Set<? extends Object> arg2) { }
+                    }
+                    """
+                )
+            ),
+
+            source = """
+                package test.pkg;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public class MoreAsserts {
+                public MoreAsserts() { throw new RuntimeException("Stub!"); }
+                public static void assertEquals(java.lang.String arg0, java.util.Set<?> arg1, java.util.Set<?> arg2) { throw new RuntimeException("Stub!"); }
+                public static void assertEquals(java.util.Set<?> arg1, java.util.Set<?> arg2) { throw new RuntimeException("Stub!"); }
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Generate stubs enum instance methods`() {
+        checkStubs(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+
+                    public enum ChronUnit implements TempUnit {
+                        C(1), B(2), A(3);
+
+                        ChronUnit(int y) {
+                        }
+
+                        public String valueOf(int x) {
+                            return Integer.toString(x + 5);
+                        }
+
+                        public String values(String separator) {
+                            return null;
+                        }
+
+                        @Override
+                        public String toString() {
+                            return name();
+                        }
+                    }
+                    """
+                ), java(
+                    """
+                    package test.pkg;
+
+                    public interface TempUnit {
+                        @Override
+                        String toString();
+                    }
+                     """
+                )
+            ),
+            source = """
+                package test.pkg;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public enum ChronUnit implements test.pkg.TempUnit {
+                C, B, A;
+                public java.lang.String valueOf(int x) { throw new RuntimeException("Stub!"); }
+                public java.lang.String toString() { throw new RuntimeException("Stub!"); }
+                }
+            """
+        )
+    }
+
+    @Test
+    fun `Generate stubs with superclass filtering`() {
+        checkStubs(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    public class MyClass extends HiddenParent {
+                        public void method4() { }
+                    }
+                    """
+                ), java(
+                    """
+                    package test.pkg;
+                    /** @hide */
+                    @SuppressWarnings("ALL")
+                    public class HiddenParent extends HiddenParent2 {
+                        public static final String CONSTANT = "MyConstant";
+                        protected int mContext;
+                        public void method3() { }
+                    }
+                    """
+                ), java(
+                    """
+                    package test.pkg;
+                    /** @hide */
+                    @SuppressWarnings("ALL")
+                    public class HiddenParent2 extends PublicParent {
+                        public void method2() { }
+                    }
+                    """
+                ), java(
+                    """
+                    package test.pkg;
+                    public class PublicParent {
+                        public void method1() { }
+                    }
+                    """
+                )
+            ),
+            // Notice how the intermediate methods (method2, method3) have been removed
+            source = """
+                package test.pkg;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public class MyClass extends test.pkg.PublicParent {
+                public MyClass() { throw new RuntimeException("Stub!"); }
+                public void method4() { throw new RuntimeException("Stub!"); }
+                }
+                """,
+            warnings = "src/test/pkg/MyClass.java:2: warning: Public class test.pkg.MyClass stripped of unavailable superclass test.pkg.HiddenParent [HiddenSuperclass:111]"
+        )
+    }
+
+    @Test
+    fun `Check inheriting from package private class`() {
+        checkStubs(
+            // Disabled because doclava1 includes fields here that it doesn't include in the
+            // signature file; not sure if it's a bug or intentional but it seems suspicious.
+            //checkDoclava1 = true,
+            sourceFiles =
+            *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    public class MyClass extends HiddenParent {
+                        public void method1() { }
+                    }
+                    """
+                ), java(
+                    """
+                    package test.pkg;
+                    class HiddenParent {
+                        public static final String CONSTANT = "MyConstant";
+                        protected int mContext;
+                        public void method2() { }
+                    }
+                    """
+                )
+            ),
+            warnings = "",
+            source = """
+                    package test.pkg;
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public class MyClass {
+                    public MyClass() { throw new RuntimeException("Stub!"); }
+                    public void method1() { throw new RuntimeException("Stub!"); }
+                    }
+                """
+        )
+    }
+
+    @Test
+    fun `Check implementing a package private interface`() {
+        // If you implement a package private interface, we just remove it and inline the members into
+        // the subclass
+
+        // BUG: Note that we need to implement the parent
+        checkStubs(
+            compatibilityMode = true,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    public class MyClass implements HiddenInterface {
+                        @Override public void method() { }
+                        @Override public void other() { }
+                    }
+                    """
+                ), java(
+                    """
+                    package test.pkg;
+                    public interface OtherInterface {
+                        void other();
+                    }
+                    """
+                ), java(
+                    """
+                    package test.pkg;
+                    interface HiddenInterface extends OtherInterface {
+                        void method() { }
+                        String CONSTANT = "MyConstant";
+                    }
+                    """
+                )
+            ),
+            source = """
+                package test.pkg;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public class MyClass implements test.pkg.OtherInterface {
+                public MyClass() { throw new RuntimeException("Stub!"); }
+                public void method() { throw new RuntimeException("Stub!"); }
+                public void other() { throw new RuntimeException("Stub!"); }
+                public static final java.lang.String CONSTANT = "MyConstant";
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Check throws list`() {
+        // Make sure we format a throws list
+        checkStubs(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    import java.io.IOException;
+
+                    @SuppressWarnings("RedundantThrows")
+                    public abstract class AbstractCursor {
+                        @Override protected void finalize1() throws Throwable { }
+                        @Override protected void finalize2() throws IOException, IllegalArgumentException {  }
+                    }
+                    """
+                )
+            ),
+            source = """
+                package test.pkg;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public abstract class AbstractCursor {
+                public AbstractCursor() { throw new RuntimeException("Stub!"); }
+                protected void finalize1() throws java.lang.Throwable { throw new RuntimeException("Stub!"); }
+                protected void finalize2() throws java.io.IOException, java.lang.IllegalArgumentException { throw new RuntimeException("Stub!"); }
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Check generating constants in interface without inline-able initializers`() {
+        checkStubs(
+            sourceFiles =
+            *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    public interface MyClass {
+                        String[] CONSTANT1 = {"MyConstant","MyConstant2"};
+                        boolean CONSTANT2 = Boolean.getBoolean(System.getenv("VAR1"));
+                        int CONSTANT3 = Integer.parseInt(System.getenv("VAR2"));
+                        String CONSTANT4 = null;
+                    }
+                    """
+                )
+            ),
+            warnings = "",
+            source = """
+                package test.pkg;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public interface MyClass {
+                public static final java.lang.String[] CONSTANT1 = null;
+                public static final boolean CONSTANT2 = false;
+                public static final int CONSTANT3 = 0; // 0x0
+                public static final java.lang.String CONSTANT4 = null;
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Handle non-constant fields in final classes`() {
+        checkStubs(
+            checkDoclava1 = false,
+            sourceFiles =
+            *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+
+                    @SuppressWarnings("all")
+                    public class FinalFieldTest {
+                        public interface TemporalField {
+                            String getBaseUnit();
+                        }
+                        public static final class IsoFields {
+                            public static final TemporalField DAY_OF_QUARTER = Field.DAY_OF_QUARTER;
+                            private IsoFields() {
+                                throw new AssertionError("Not instantiable");
+                            }
+
+                            private static enum Field implements TemporalField {
+                                DAY_OF_QUARTER {
+                                    @Override
+                                    public String getBaseUnit() {
+                                        return "days";
+                                    }
+                               }
+                           };
+                        }
+                    }
+                    """
+                )
+            ),
+            warnings = "",
+            source = """
+                    package test.pkg;
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public class FinalFieldTest {
+                    public FinalFieldTest() { throw new RuntimeException("Stub!"); }
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public static final class IsoFields {
+                    IsoFields() { throw new RuntimeException("Stub!"); }
+                    public static final test.pkg.FinalFieldTest.TemporalField DAY_OF_QUARTER;
+                    static { DAY_OF_QUARTER = null; }
+                    }
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public static interface TemporalField {
+                    public java.lang.String getBaseUnit();
+                    }
+                    }
+                    """
+        )
+    }
+
+    @Test
+    fun `Test final instance fields`() {
+        // Instance fields in a class must be initialized
+        checkStubs(
+            checkDoclava1 = false,
+            sourceFiles =
+            *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+
+                    @SuppressWarnings("all")
+                    public class InstanceFieldTest {
+                        public static final class WindowLayout {
+                            public WindowLayout(int width, int height, int gravity) {
+                                this.width = width;
+                                this.height = height;
+                                this.gravity = gravity;
+                            }
+
+                            public final int width;
+                            public final int height;
+                            public final int gravity;
+
+                        }
+                    }
+                    """
+                )
+            ),
+            warnings = "",
+            source = """
+                    package test.pkg;
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public class InstanceFieldTest {
+                    public InstanceFieldTest() { throw new RuntimeException("Stub!"); }
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public static final class WindowLayout {
+                    public WindowLayout(int width, int height, int gravity) { throw new RuntimeException("Stub!"); }
+                    public final int gravity;
+                    { gravity = 0; }
+                    public final int height;
+                    { height = 0; }
+                    public final int width;
+                    { width = 0; }
+                    }
+                    }
+                    """
+        )
+    }
+
+    @Test
+    fun `Check generating constants in class without inline-able initializers`() {
+        checkStubs(
+            sourceFiles =
+            *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    public class MyClass {
+                        public static String[] CONSTANT1 = {"MyConstant","MyConstant2"};
+                        public static boolean CONSTANT2 = Boolean.getBoolean(System.getenv("VAR1"));
+                        public static int CONSTANT3 = Integer.parseInt(System.getenv("VAR2"));
+                        public static String CONSTANT4 = null;
+                    }
+                    """
+                )
+            ),
+            warnings = "",
+            source = """
+                package test.pkg;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public class MyClass {
+                public MyClass() { throw new RuntimeException("Stub!"); }
+                public static java.lang.String[] CONSTANT1;
+                public static boolean CONSTANT2;
+                public static int CONSTANT3;
+                public static java.lang.String CONSTANT4;
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Check generating annotation source`() {
+        checkStubs(
+            sourceFiles =
+            *arrayOf(
+                java(
+                    """
+                    package android.view.View;
+                    import android.annotation.IntDef;
+                    import android.annotation.IntRange;
+                    import java.lang.annotation.Retention;
+                    import java.lang.annotation.RetentionPolicy;
+                    public class View {
+                        @SuppressWarnings("all")
+                        public static class MeasureSpec {
+                            private static final int MODE_SHIFT = 30;
+                            private static final int MODE_MASK  = 0x3 << MODE_SHIFT;
+                            /** @hide */
+                            @SuppressWarnings("all")
+                            @IntDef({UNSPECIFIED, EXACTLY, AT_MOST})
+                            @Retention(RetentionPolicy.SOURCE)
+                            public @interface MeasureSpecMode {}
+                            public static final int UNSPECIFIED = 0 << MODE_SHIFT;
+                            public static final int EXACTLY     = 1 << MODE_SHIFT;
+                            public static final int AT_MOST     = 2 << MODE_SHIFT;
+
+                            public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
+                                                              @MeasureSpecMode int mode) {
+                                return 0;
+                            }
+                        }
+                    }
+                    """
+                ),
+                intDefAnnotationSource,
+                intRangeAnnotationSource
+            ),
+            warnings = "",
+            source = """
+                    package android.view.View;
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public class View {
+                    public View() { throw new RuntimeException("Stub!"); }
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public static class MeasureSpec {
+                    public MeasureSpec() { throw new RuntimeException("Stub!"); }
+                    /**
+                     * @param size Value is between 0 and (1 << MeasureSpec.MODE_SHIFT) - 1 inclusive
+                     * @param mode Value is {@link android.view.View.View.MeasureSpec#UNSPECIFIED}, {@link android.view.View.View.MeasureSpec#EXACTLY}, or {@link android.view.View.View.MeasureSpec#AT_MOST}
+                     */
+                    public static int makeMeasureSpec(@android.support.annotation.IntRange(from=0, to=0x40000000 - 1) int size, int mode) { throw new RuntimeException("Stub!"); }
+                    public static final int AT_MOST = -2147483648; // 0x80000000
+                    public static final int EXACTLY = 1073741824; // 0x40000000
+                    public static final int UNSPECIFIED = 0; // 0x0
+                    }
+                    }
+                """
+        )
+    }
+
+    @Test
+    fun `Check generating classes with generics`() {
+        checkStubs(
+            sourceFiles =
+            *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+
+                    public class Generics {
+                        public <T> Generics(int surfaceSize, Class<T> klass) {
+                        }
+                    }
+                    """
+                )
+            ),
+            warnings = "",
+            source = """
+                    package test.pkg;
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public class Generics {
+                    public <T> Generics(int surfaceSize, java.lang.Class<T> klass) { throw new RuntimeException("Stub!"); }
+                    }
+                """
+        )
+    }
+
+    @Test
+    fun `Check generating annotation for hidden constants`() {
+        checkStubs(
+            sourceFiles =
+            *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+
+                    import android.content.Intent;
+                    import android.annotation.RequiresPermission;
+
+                    public abstract class HiddenPermission {
+                        @RequiresPermission(allOf = {
+                                android.Manifest.permission.INTERACT_ACROSS_USERS,
+                                android.Manifest.permission.BROADCAST_STICKY
+                        })
+                        public abstract void removeStickyBroadcast(@RequiresPermission Object intent);
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package android;
+
+                    public final class Manifest {
+                        @SuppressWarnings("JavaDoc")
+                        public static final class permission {
+                            public static final String BROADCAST_STICKY = "android.permission.BROADCAST_STICKY";
+                            /** @SystemApi @hide Allows an application to call APIs that allow it to do interactions
+                             across the users on the device, using singleton services and
+                             user-targeted broadcasts.  This permission is not available to
+                             third party applications. */
+                            public static final String INTERACT_ACROSS_USERS = "android.permission.INTERACT_ACROSS_USERS";
+                        }
+                    }
+                    """
+                ),
+                requiresPermissionSource
+            ),
+            warnings = "src/test/pkg/HiddenPermission.java:5: lint: Permission android.Manifest.permission.INTERACT_ACROSS_USERS required by method test.pkg.HiddenPermission.removeStickyBroadcast(Object) is hidden or removed [MissingPermission:132]",
+            source = """
+                    package test.pkg;
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public abstract class HiddenPermission {
+                    public HiddenPermission() { throw new RuntimeException("Stub!"); }
+                    /**
+                     * Requires {@link android.Manifest.permission#INTERACT_ACROSS_USERS} and {@link android.Manifest.permission#BROADCAST_STICKY}
+                     */
+                    @android.support.annotation.RequiresPermission(allOf={"android.permission.INTERACT_ACROSS_USERS", android.Manifest.permission.BROADCAST_STICKY}) public abstract void removeStickyBroadcast(@android.support.annotation.RequiresPermission java.lang.Object intent);
+                    }
+                """
+        )
+    }
+
+    @Test
+    fun `Check generating type parameters in interface list`() {
+        // In signature files we don't include generics in the interface list.
+        // In stubs, we do.
+        checkStubs(
+            sourceFiles =
+            *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+
+                    @SuppressWarnings("NullableProblems")
+                    public class GenericsInInterfaces<T> implements Comparable<GenericsInInterfaces> {
+                        @Override
+                        public int compareTo(GenericsInInterfaces o) {
+                            return 0;
+                        }
+
+                        void foo(T bar) {
+                        }
+                    }
+                    """
+                )
+            ),
+            api = """
+                package test.pkg {
+                  public class GenericsInInterfaces<T> implements java.lang.Comparable {
+                    ctor public GenericsInInterfaces();
+                    method public int compareTo(test.pkg.GenericsInInterfaces);
+                  }
+                }
+                """,
+            source = """
+                package test.pkg;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public class GenericsInInterfaces<T> implements java.lang.Comparable<test.pkg.GenericsInInterfaces> {
+                public GenericsInInterfaces() { throw new RuntimeException("Stub!"); }
+                public int compareTo(test.pkg.GenericsInInterfaces o) { throw new RuntimeException("Stub!"); }
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Preserve file header comments`() {
+        checkStubs(
+            sourceFiles =
+            *arrayOf(
+                java(
+                    """
+                    /*
+                    My header 1
+                     */
+
+                    /*
+                    My header 2
+                     */
+
+                    // My third comment
+
+                    package test.pkg;
+
+                    public class HeaderComments {
+                    }
+                    """
+                )
+            ),
+            source = """
+                    /*
+                    My header 1
+                     */
+                    /*
+                    My header 2
+                     */
+                    // My third comment
+                    package test.pkg;
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public class HeaderComments {
+                    public HeaderComments() { throw new RuntimeException("Stub!"); }
+                    }
+                    """
+        )
+    }
+
+    @Test
+    fun `Basic Kotlin class`() {
+        checkStubs(
+            sourceFiles = *arrayOf(
+                kotlin(
+                    """
+                    /* My file header */
+                    // Another comment
+                    @file:JvmName("Driver")
+                    package test.pkg
+                    /** My class doc */
+                    class Kotlin(val property1: String = "Default Value", arg2: Int) : Parent() {
+                        override fun method() = "Hello World"
+                        /** My method doc */
+                        fun otherMethod(ok: Boolean, times: Int) {
+                        }
+
+                        /** property doc */
+                        var property2: String? = null
+
+                        /** @hide */
+                        var hiddenProperty: String? = "hidden"
+
+                        private var someField = 42
+                        @JvmField
+                        var someField2 = 42
+                    }
+
+                    open class Parent {
+                        open fun method(): String? = null
+                        open fun method2(value1: Boolean, value2: Boolean?): String? = null
+                        open fun method3(value1: Int?, value2: Int): Int = null
+                    }
+                    """
+                )
+            ),
+            source = """
+                    /* My file header */
+                    // Another comment
+                    package test.pkg;
+                    /** My class doc */
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public final class Kotlin extends test.pkg.Parent {
+                    public Kotlin(@android.support.annotation.NonNull java.lang.String property1, int arg2) { throw new RuntimeException("Stub!"); }
+                    @android.support.annotation.NonNull public java.lang.String method() { throw new RuntimeException("Stub!"); }
+                    /** My method doc */
+                    public final void otherMethod(boolean ok, int times) { throw new RuntimeException("Stub!"); }
+                    /** property doc */
+                    @android.support.annotation.Nullable public final java.lang.String getProperty2() { throw new RuntimeException("Stub!"); }
+                    /** property doc */
+                    public final void setProperty2(@android.support.annotation.Nullable java.lang.String p) { throw new RuntimeException("Stub!"); }
+                    @android.support.annotation.NonNull public final java.lang.String getProperty1() { throw new RuntimeException("Stub!"); }
+                    public int someField2;
+                    }
+                """,
+            checkDoclava1 = false /* doesn't support Kotlin... */
+        )
+    }
+
+    @Test
+    fun `Parameter Names in Java`() {
+        // Java code which explicitly specifies parameter names: make sure stub uses
+        // parameter name
+        checkStubs(
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    import android.support.annotation.ParameterName;
+
+                    public class Foo {
+                        public void foo(int javaParameter1, @ParameterName("publicParameterName") int javaParameter2) {
+                        }
+                    }
+                    """
+                ),
+                supportParameterName
+            ),
+            source = """
+                package test.pkg;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public class Foo {
+                public Foo() { throw new RuntimeException("Stub!"); }
+                public void foo(int javaParameter1, int publicParameterName) { throw new RuntimeException("Stub!"); }
+                }
+                 """,
+            checkDoclava1 = false /* doesn't support parameter names */
+        )
+    }
+
+    @Test
+    fun `Remove Hidden Annotations`() {
+        // When APIs reference annotations that are hidden, make sure the're excluded from the stubs and
+        // signature files
+        checkStubs(
+            compatibilityMode = false,
+            checkDoclava1 = false, // doesn't support type-use annotations
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+
+                    public class Foo {
+                        public void foo(int p1, @MyAnnotation("publicParameterName") java.util.Map<String, @MyAnnotation("Something") String> p2) {
+                        }
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg;
+                    import java.lang.annotation.*;
+                    import static java.lang.annotation.ElementType.*;
+                    import static java.lang.annotation.RetentionPolicy.SOURCE;
+                    /** @hide */
+                    @SuppressWarnings("WeakerAccess")
+                    @Retention(SOURCE)
+                    @Target({METHOD, PARAMETER, FIELD, TYPE_USE})
+                    public @interface MyAnnotation {
+                        String value();
+                    }
+                    """
+                )
+            ),
+            api = """
+                package test.pkg {
+                  public class Foo {
+                    ctor public Foo();
+                    method public void foo(int, java.util.Map<String, java.lang.String>!);
+                  }
+                }
+                """,
+
+            source = """
+                package test.pkg;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public class Foo {
+                public Foo() { throw new RuntimeException("Stub!"); }
+                public void foo(int p1, java.util.Map<java.lang.String, java.lang.String> p2) { throw new RuntimeException("Stub!"); }
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Arguments to super constructors`() {
+        // When overriding constructors we have to supply arguments
+        checkStubs(
+            sourceFiles =
+            *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+
+                    @SuppressWarnings("WeakerAccess")
+                    public class Constructors {
+                        public class Parent {
+                            public Parent(String s, int i, long l, boolean b, short sh) {
+                            }
+                        }
+
+                        public class Child extends Parent {
+                            public Child(String s, int i, long l, boolean b, short sh) {
+                                super(s, i, l, b, sh);
+                            }
+
+                            private Child(String s) {
+                                super(s, 0, 0, false, 0);
+                            }
+                        }
+
+                        public class Child2 extends Parent {
+                            Child2(String s) {
+                                super(s, 0, 0, false, 0);
+                            }
+                        }
+
+                        public class Child3 extends Child2 {
+                            private Child3(String s) {
+                                super("something");
+                            }
+                        }
+
+                        public class Child4 extends Parent {
+                            Child4(String s, HiddenClass hidden) {
+                                super(s, 0, 0, true, 0);
+                            }
+                        }
+                        /** @hide */
+                        public class HiddenClass {
+                        }
+                    }
+                    """
+                )
+            ),
+            source = """
+                    package test.pkg;
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public class Constructors {
+                    public Constructors() { throw new RuntimeException("Stub!"); }
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public class Child extends test.pkg.Constructors.Parent {
+                    public Child(java.lang.String s, int i, long l, boolean b, short sh) { super(null, 0, 0, false, (short)0); throw new RuntimeException("Stub!"); }
+                    }
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public class Child2 extends test.pkg.Constructors.Parent {
+                    Child2(java.lang.String s) { super(null, 0, 0, false, (short)0); throw new RuntimeException("Stub!"); }
+                    }
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public class Child3 extends test.pkg.Constructors.Child2 {
+                    Child3(java.lang.String s) { super(null); throw new RuntimeException("Stub!"); }
+                    }
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public class Child4 extends test.pkg.Constructors.Parent {
+                    Child4() { super(null, 0, 0, false, (short)0); throw new RuntimeException("Stub!"); }
+                    }
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public class Parent {
+                    public Parent(java.lang.String s, int i, long l, boolean b, short sh) { throw new RuntimeException("Stub!"); }
+                    }
+                    }
+                    """
+        )
+    }
+
+    // TODO: Add test to see what happens if I have Child4 in a different package which can't access the package private constructor of child3?
+
+    @Test
+    fun `DocOnly members should be omitted`() {
+        // When marked @doconly don't include in stubs or signature files
+        // unless specifically asked for (which we do when generating docs).
+        checkStubs(
+            sourceFiles =
+            *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+
+                    @SuppressWarnings("JavaDoc")
+                    public class Outer {
+                        /** @doconly Some docs here */
+                        public class MyClass1 {
+                            public int myField;
+                        }
+
+                        public class MyClass2 {
+                            /** @doconly Some docs here */
+                            public int myField;
+
+                            /** @doconly Some docs here */
+                            public int myMethod() { return 0; }
+                        }
+                    }
+                    """
+                )
+            ),
+            source = """
+                package test.pkg;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public class Outer {
+                public Outer() { throw new RuntimeException("Stub!"); }
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public class MyClass2 {
+                public MyClass2() { throw new RuntimeException("Stub!"); }
+                }
+                }
+                    """,
+            api = """
+                package test.pkg {
+                  public class Outer {
+                    ctor public Outer();
+                  }
+                  public class Outer.MyClass2 {
+                    ctor public Outer.MyClass2();
+                  }
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `DocOnly members should be included when requested`() {
+        // When marked @doconly don't include in stubs or signature files
+        // unless specifically asked for (which we do when generating docs).
+        checkStubs(
+            extraArguments = arrayOf("--include-doconly"),
+            sourceFiles =
+            *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+
+                    @SuppressWarnings("JavaDoc")
+                    public class Outer {
+                        /** @doconly Some docs here */
+                        public class MyClass1 {
+                            public int myField;
+                        }
+
+                        public class MyClass2 {
+                            /** @doconly Some docs here */
+                            public int myField;
+
+                            /** @doconly Some docs here */
+                            public int myMethod() { return 0; }
+                        }
+                    }
+                    """
+                )
+            ),
+            source = """
+                    package test.pkg;
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public class Outer {
+                    public Outer() { throw new RuntimeException("Stub!"); }
+                    /** @doconly Some docs here */
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public class MyClass1 {
+                    public MyClass1() { throw new RuntimeException("Stub!"); }
+                    public int myField;
+                    }
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public class MyClass2 {
+                    public MyClass2() { throw new RuntimeException("Stub!"); }
+                    /** @doconly Some docs here */
+                    public int myMethod() { throw new RuntimeException("Stub!"); }
+                    /** @doconly Some docs here */
+                    public int myField;
+                    }
+                    }
+                    """
+        )
+    }
+
+    @Test
+    fun `Check generating required stubs from hidden super classes and interfaces`() {
+        checkStubs(
+            checkDoclava1 = false,
+            sourceFiles =
+            *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    public class MyClass extends HiddenSuperClass implements HiddenInterface, PublicInterface2 {
+                        public void myMethod() { }
+                        @Override public void publicInterfaceMethod2() { }
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg;
+                    class HiddenSuperClass extends PublicSuperParent {
+                        @Override public void inheritedMethod2() { }
+                        @Override public void publicInterfaceMethod() { }
+                        @Override public void publicMethod() {}
+                        @Override public void publicMethod2() {}
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg;
+                    public abstract class PublicSuperParent {
+                        public void inheritedMethod1() {}
+                        public void inheritedMethod2() {}
+                        public abstract void publicMethod() {}
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg;
+                    interface HiddenInterface extends PublicInterface {
+                        int MY_CONSTANT = 5;
+                        void hiddenInterfaceMethod();
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg;
+                    public interface PublicInterface {
+                        void publicInterfaceMethod();
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg;
+                    public interface PublicInterface2 {
+                        void publicInterfaceMethod2();
+                    }
+                    """
+                )
+            ),
+            warnings = "",
+            api = """
+                    package test.pkg {
+                      public class MyClass extends test.pkg.PublicSuperParent implements test.pkg.PublicInterface test.pkg.PublicInterface2 {
+                        ctor public MyClass();
+                        method public void myMethod();
+                        method public void publicInterfaceMethod2();
+                        field public static final int MY_CONSTANT = 5; // 0x5
+                      }
+                      public abstract interface PublicInterface {
+                        method public abstract void publicInterfaceMethod();
+                      }
+                      public abstract interface PublicInterface2 {
+                        method public abstract void publicInterfaceMethod2();
+                      }
+                      public abstract class PublicSuperParent {
+                        ctor public PublicSuperParent();
+                        method public void inheritedMethod1();
+                        method public void inheritedMethod2();
+                        method public abstract void publicMethod();
+                      }
+                    }
+                """,
+            source = """
+                package test.pkg;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public class MyClass extends test.pkg.PublicSuperParent implements test.pkg.PublicInterface, test.pkg.PublicInterface2 {
+                public MyClass() { throw new RuntimeException("Stub!"); }
+                public void myMethod() { throw new RuntimeException("Stub!"); }
+                public void publicInterfaceMethod2() { throw new RuntimeException("Stub!"); }
+                // Inlined stub from hidden parent class test.pkg.HiddenSuperClass
+                public void publicMethod() { throw new RuntimeException("Stub!"); }
+                // Inlined stub from hidden parent class test.pkg.HiddenSuperClass
+                public void publicInterfaceMethod() { throw new RuntimeException("Stub!"); }
+                public static final int MY_CONSTANT = 5; // 0x5
+                }
+                """
+        )
+    }
+
+    @Test
+    fun `Rewrite libcore annotations`() {
+        check(
+            checkDoclava1 = false,
+            checkCompilation = true,
+            sourceFiles = *arrayOf(
+                java(
+                    "package my.pkg;\n" +
+                            "public class String {\n" +
+                            "public String(char @libcore.util.NonNull [] value) { throw new RuntimeException(\"Stub!\"); }\n" +
+                            "}\n"
+                )
+            ),
+            warnings = "",
+            api = """
+                    package my.pkg {
+                      public class String {
+                        ctor public String(char[]);
+                      }
+                    }
+                    """,
+            stubs = arrayOf(
+                """
+                package my.pkg;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public class String {
+                public String(char @android.support.annotation.NonNull [] value) { throw new RuntimeException("Stub!"); }
+                }
+                """
+            )
+        )
+    }
+
+    @Test
+    fun `Test inaccessible constructors`() {
+        // If the constructors of a class are not visible, and the class has subclasses,
+        // those subclass stubs will need to reference these inaccessible constructors.
+        // This generally only happens when the constructors are package private (and
+        // therefore hidden) but the subclass using it is also in the same package.
+
+        check(
+            checkDoclava1 = false,
+            checkCompilation = true,
+            sourceFiles =
+            *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    public class MyClass1 {
+                        MyClass1(int myVar) { }
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg;
+                    import java.io.IOException;
+                    @SuppressWarnings("RedundantThrows")
+                    public class MySubClass1 extends MyClass1 {
+                        MySubClass1(int myVar) throws IOException { super(myVar); }
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg;
+                    public class MyClass2 {
+                        /** @hide */
+                        public MyClass2(int myVar) { }
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg;
+                    public class MySubClass2 extends MyClass2 {
+                        public MySubClass2() { super(5); }
+                    }
+                    """
+                )
+            ),
+            warnings = "",
+            api = """
+                    package test.pkg {
+                      public class MyClass1 {
+                      }
+                      public class MyClass2 {
+                      }
+                      public class MySubClass1 extends test.pkg.MyClass1 {
+                      }
+                      public class MySubClass2 extends test.pkg.MyClass2 {
+                        ctor public MySubClass2();
+                      }
+                    }
+                    """,
+            stubs = arrayOf(
+                """
+                package test.pkg;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public class MyClass1 {
+                MyClass1(int myVar) { throw new RuntimeException("Stub!"); }
+                }
+                """,
+                """
+                package test.pkg;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public class MySubClass1 extends test.pkg.MyClass1 {
+                MySubClass1(int myVar) throws java.io.IOException { super(0); throw new RuntimeException("Stub!"); }
+                }
+                """,
+                """
+                package test.pkg;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public class MyClass2 {
+                /** @hide */
+                MyClass2(int myVar) { throw new RuntimeException("Stub!"); }
+                }
+                """,
+                """
+                package test.pkg;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public class MySubClass2 extends test.pkg.MyClass2 {
+                public MySubClass2() { super(0); throw new RuntimeException("Stub!"); }
+                }
+                """
+            )
+        )
+    }
+
+    @Test
+    fun `Generics Variable Rewriting`() {
+        // When we move methods from hidden superclasses into the subclass since they
+        // provide the implementation for a required method, it's possible that the
+        // method we copied in is referencing generics with a different variable than
+        // in the current class, so we need to handle this
+
+        checkStubs(
+            checkDoclava1 = false,
+            sourceFiles =
+            *arrayOf(
+                // TODO: Try using prefixes like "A", and "AA" to make sure my generics
+                // variable renaming doesn't do something really dumb
+                java(
+                    """
+                    package test.pkg;
+
+                    import java.util.List;
+                    import java.util.Map;
+
+                    public class Generics {
+                        public class MyClass<X extends Number,Y> extends HiddenParent<X,Y> implements PublicParent<X,Y> {
+                        }
+
+                        public class MyClass2<W> extends HiddenParent<Float,W> implements PublicParent<Float, W> {
+                        }
+
+                        public class MyClass3 extends HiddenParent<Float,Double> implements PublicParent<Float,Double> {
+                        }
+
+                        class HiddenParent<M, N> extends HiddenParent2<M, N>  {
+                        }
+
+                        class HiddenParent2<T, TT>  {
+                            public Map<T,Map<TT, String>> createMap(List<T> list) {
+                                return null;
+                            }
+                        }
+
+                        public interface PublicParent<A extends Number,B> {
+                            Map<A,Map<B, String>> createMap(List<A> list);
+                        }
+                    }
+                    """
+                )
+            ),
+            warnings = "",
+            source = """
+                    package test.pkg;
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public class Generics {
+                    public Generics() { throw new RuntimeException("Stub!"); }
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public class MyClass<X extends java.lang.Number, Y> implements test.pkg.Generics.PublicParent<X,Y> {
+                    public MyClass() { throw new RuntimeException("Stub!"); }
+                    // Inlined stub from hidden parent class test.pkg.Generics.HiddenParent2
+                    public java.util.Map<X,java.util.Map<Y,java.lang.String>> createMap(java.util.List<X> list) { throw new RuntimeException("Stub!"); }
+                    }
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public class MyClass2<W> implements test.pkg.Generics.PublicParent<java.lang.Float,W> {
+                    public MyClass2() { throw new RuntimeException("Stub!"); }
+                    // Inlined stub from hidden parent class test.pkg.Generics.HiddenParent2
+                    public java.util.Map<java.lang.Float,java.util.Map<W,java.lang.String>> createMap(java.util.List<java.lang.Float> list) { throw new RuntimeException("Stub!"); }
+                    }
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public class MyClass3 implements test.pkg.Generics.PublicParent<java.lang.Float,java.lang.Double> {
+                    public MyClass3() { throw new RuntimeException("Stub!"); }
+                    // Inlined stub from hidden parent class test.pkg.Generics.HiddenParent2
+                    public java.util.Map<java.lang.Float,java.util.Map<java.lang.Double,java.lang.String>> createMap(java.util.List<java.lang.Float> list) { throw new RuntimeException("Stub!"); }
+                    }
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public static interface PublicParent<A extends java.lang.Number, B> {
+                    public java.util.Map<A,java.util.Map<B,java.lang.String>> createMap(java.util.List<A> list);
+                    }
+                    }
+                    """
+        )
+    }
+
+    @Test
+    fun `Rewriting type parameters in interfaces from hidden super classes and in throws lists`() {
+        checkStubs(
+            checkDoclava1 = false,
+            sourceFiles =
+            *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+
+                    import java.io.IOException;
+                    import java.util.List;
+                    import java.util.Map;
+
+                    @SuppressWarnings({"RedundantThrows", "WeakerAccess"})
+                    public class Generics {
+                        public class MyClass<X, Y extends Number> extends HiddenParent<X, Y> implements PublicInterface<X, Y> {
+                        }
+
+                        class HiddenParent<M, N extends Number> extends PublicParent<M, N> {
+                            public Map<M, Map<N, String>> createMap(List<M> list) throws MyThrowable {
+                                return null;
+                            }
+
+                            protected List<M> foo() {
+                                return null;
+                            }
+
+                        }
+
+                        class MyThrowable extends IOException {
+                        }
+
+                        public abstract class PublicParent<A, B extends Number> {
+                            protected abstract List<A> foo();
+                        }
+
+                        public interface PublicInterface<A, B> {
+                            Map<A, Map<B, String>> createMap(List<A> list) throws IOException;
+                        }
+                    }
+                    """
+                )
+            ),
+            warnings = "",
+            api = """
+                    package test.pkg {
+                      public class Generics {
+                        ctor public Generics();
+                      }
+                      public class Generics.MyClass<X, Y extends java.lang.Number> extends test.pkg.Generics.PublicParent implements test.pkg.Generics.PublicInterface {
+                        ctor public Generics.MyClass();
+                      }
+                      public static abstract interface Generics.PublicInterface<A, B> {
+                        method public abstract java.util.Map<A, java.util.Map<B, java.lang.String>> createMap(java.util.List<A>) throws java.io.IOException;
+                      }
+                      public abstract class Generics.PublicParent<A, B extends java.lang.Number> {
+                        ctor public Generics.PublicParent();
+                        method protected abstract java.util.List<A> foo();
+                      }
+                    }
+                    """,
+            source = """
+                    package test.pkg;
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public class Generics {
+                    public Generics() { throw new RuntimeException("Stub!"); }
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public class MyClass<X, Y extends java.lang.Number> extends test.pkg.Generics.PublicParent<X,Y> implements test.pkg.Generics.PublicInterface<X,Y> {
+                    public MyClass() { throw new RuntimeException("Stub!"); }
+                    // Inlined stub from hidden parent class test.pkg.Generics.HiddenParent
+                    public java.util.List<X> foo() { throw new RuntimeException("Stub!"); }
+                    // Inlined stub from hidden parent class test.pkg.Generics.HiddenParent
+                    public java.util.Map<X,java.util.Map<Y,java.lang.String>> createMap(java.util.List<X> list) { throw new RuntimeException("Stub!"); }
+                    }
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public static interface PublicInterface<A, B> {
+                    public java.util.Map<A,java.util.Map<B,java.lang.String>> createMap(java.util.List<A> list) throws java.io.IOException;
+                    }
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public abstract class PublicParent<A, B extends java.lang.Number> {
+                    public PublicParent() { throw new RuntimeException("Stub!"); }
+                    protected abstract java.util.List<A> foo();
+                    }
+                    }
+                    """
+        )
+    }
+
+    @Test
+    fun `Rewriting implements class references`() {
+        // Checks some more subtle bugs around generics type variable renaming
+        checkStubs(
+            checkDoclava1 = false,
+            sourceFiles =
+            *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+
+                    import java.util.Collection;
+                    import java.util.Set;
+
+                    @SuppressWarnings("all")
+                    public class ConcurrentHashMap<K, V> {
+                        public abstract static class KeySetView<K, V> extends CollectionView<K, V, K>
+                                implements Set<K>, java.io.Serializable {
+                        }
+
+                        abstract static class CollectionView<K, V, E>
+                                implements Collection<E>, java.io.Serializable {
+                            public final Object[] toArray() { return null; }
+
+                            public final <T> T[] toArray(T[] a) {
+                                return null;
+                            }
+
+                            @Override
+                            public int size() {
+                                return 0;
+                            }
+                        }
+                    }
+                    """
+                )
+            ),
+            warnings = "",
+            api = """
+                    package test.pkg {
+                      public class ConcurrentHashMap<K, V> {
+                        ctor public ConcurrentHashMap();
+                      }
+                      public static abstract class ConcurrentHashMap.KeySetView<K, V> implements java.util.Collection java.io.Serializable java.util.Set {
+                        ctor public ConcurrentHashMap.KeySetView();
+                      }
+                    }
+                    """,
+            source = """
+                    package test.pkg;
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public class ConcurrentHashMap<K, V> {
+                    public ConcurrentHashMap() { throw new RuntimeException("Stub!"); }
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public abstract static class KeySetView<K, V> implements java.util.Collection<K>, java.io.Serializable, java.util.Set<K> {
+                    public KeySetView() { throw new RuntimeException("Stub!"); }
+                    // Inlined stub from hidden parent class test.pkg.ConcurrentHashMap.CollectionView
+                    public int size() { throw new RuntimeException("Stub!"); }
+                    // Inlined stub from hidden parent class test.pkg.ConcurrentHashMap.CollectionView
+                    public final java.lang.Object[] toArray() { throw new RuntimeException("Stub!"); }
+                    // Inlined stub from hidden parent class test.pkg.ConcurrentHashMap.CollectionView
+                    public final <T> T[] toArray(T[] a) { throw new RuntimeException("Stub!"); }
+                    }
+                    }
+                    """
+        )
+    }
+
+    @Test
+    fun `Arrays in type arguments`() {
+        checkStubs(
+            checkDoclava1 = false,
+            sourceFiles =
+            *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+
+                    public class Generics2 {
+                        public class FloatArrayEvaluator implements TypeEvaluator<float[]> {
+                        }
+
+                        @SuppressWarnings("WeakerAccess")
+                        public interface TypeEvaluator<T> {
+                        }
+                    }
+                    """
+                )
+            ),
+            warnings = "",
+            source = """
+                    package test.pkg;
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public class Generics2 {
+                    public Generics2() { throw new RuntimeException("Stub!"); }
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public class FloatArrayEvaluator implements test.pkg.Generics2.TypeEvaluator<float[]> {
+                    public FloatArrayEvaluator() { throw new RuntimeException("Stub!"); }
+                    }
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public static interface TypeEvaluator<T> {
+                    }
+                    }
+                    """
+        )
+    }
+
+    @Test
+    fun `Interface extending multiple interfaces`() {
+        // Ensure that we handle sorting correctly where we're mixing super classes and implementing
+        // interfaces
+        // Real-world example: XmlResourceParser
+        check(
+            checkDoclava1 = false,
+            checkCompilation = true,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package android.content.res;
+                    import android.util.AttributeSet;
+                    import org.xmlpull.v1.XmlPullParser;
+
+                    public interface XmlResourceParser extends XmlPullParser, AttributeSet, AutoCloseable {
+                        public void close();
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package android.util;
+                    public interface AttributeSet {
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package java.lang;
+                    public interface AutoCloseable {
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package org.xmlpull.v1;
+                    public interface XmlPullParser {
+                    }
+                    """
+                )
+            ),
+            stubs = arrayOf(
+                """
+                package android.content.res;
+                @SuppressWarnings({"unchecked", "deprecation", "all"})
+                public interface XmlResourceParser extends org.xmlpull.v1.XmlPullParser,  android.util.AttributeSet, java.lang.AutoCloseable {
+                public void close();
+                }
+                """
+            )
+        )
+    }
+
+    // TODO: Add a protected constructor too to make sure my code to make non-public constructors package private
+    // don't accidentally demote protected constructors to package private!
+
+    @Test
+    fun `Picking Super Constructors`() {
+        checkStubs(
+            checkDoclava1 = false,
+            sourceFiles =
+            *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+
+                    import java.io.IOException;
+
+                    @SuppressWarnings({"RedundantThrows", "JavaDoc", "WeakerAccess"})
+                    public class PickConstructors {
+                        public abstract static class FileInputStream extends InputStream {
+
+                            public FileInputStream(String name) throws FileNotFoundException {
+                            }
+
+                            public FileInputStream(File file) throws FileNotFoundException {
+                            }
+
+                            public FileInputStream(FileDescriptor fdObj) {
+                                this(fdObj, false /* isFdOwner */);
+                            }
+
+                            /**
+                             * @hide
+                             */
+                            public FileInputStream(FileDescriptor fdObj, boolean isFdOwner) {
+                            }
+                        }
+
+                        public abstract static class AutoCloseInputStream extends FileInputStream {
+                            public AutoCloseInputStream(ParcelFileDescriptor pfd) {
+                                super(pfd.getFileDescriptor());
+                            }
+                        }
+
+                        abstract static class HiddenParentStream extends FileInputStream {
+                            public HiddenParentStream(FileDescriptor pfd) {
+                                super(pfd);
+                            }
+                        }
+
+                        public abstract static class AutoCloseInputStream2 extends HiddenParentStream {
+                            public AutoCloseInputStream2(ParcelFileDescriptor pfd) {
+                                super(pfd.getFileDescriptor());
+                            }
+                        }
+
+                        public abstract class ParcelFileDescriptor implements Closeable {
+                            public abstract FileDescriptor getFileDescriptor();
+                        }
+
+                        public static interface Closeable extends AutoCloseable {
+                        }
+
+                        public static interface AutoCloseable {
+                        }
+
+                        public static abstract class InputStream implements Closeable {
+                        }
+
+                        public static class File {
+                        }
+
+                        public static final class FileDescriptor {
+                        }
+
+                        public static class FileNotFoundException extends IOException {
+                        }
+
+                        public static class IOException extends Exception {
+                        }
+                    }
+                    """
+                )
+            ),
+            warnings = "",
+            api = """
+                    package test.pkg {
+                      public class PickConstructors {
+                        ctor public PickConstructors();
+                      }
+                      public static abstract class PickConstructors.AutoCloseInputStream extends test.pkg.PickConstructors.FileInputStream {
+                        ctor public PickConstructors.AutoCloseInputStream(test.pkg.PickConstructors.ParcelFileDescriptor);
+                      }
+                      public static abstract class PickConstructors.AutoCloseInputStream2 extends test.pkg.PickConstructors.FileInputStream {
+                        ctor public PickConstructors.AutoCloseInputStream2(test.pkg.PickConstructors.ParcelFileDescriptor);
+                      }
+                      public static abstract interface PickConstructors.AutoCloseable {
+                      }
+                      public static abstract interface PickConstructors.Closeable implements test.pkg.PickConstructors.AutoCloseable {
+                      }
+                      public static class PickConstructors.File {
+                        ctor public PickConstructors.File();
+                      }
+                      public static final class PickConstructors.FileDescriptor {
+                        ctor public PickConstructors.FileDescriptor();
+                      }
+                      public static abstract class PickConstructors.FileInputStream extends test.pkg.PickConstructors.InputStream {
+                        ctor public PickConstructors.FileInputStream(java.lang.String) throws test.pkg.PickConstructors.FileNotFoundException;
+                        ctor public PickConstructors.FileInputStream(test.pkg.PickConstructors.File) throws test.pkg.PickConstructors.FileNotFoundException;
+                        ctor public PickConstructors.FileInputStream(test.pkg.PickConstructors.FileDescriptor);
+                      }
+                      public static class PickConstructors.FileNotFoundException extends test.pkg.PickConstructors.IOException {
+                        ctor public PickConstructors.FileNotFoundException();
+                      }
+                      public static class PickConstructors.IOException extends java.lang.Exception {
+                        ctor public PickConstructors.IOException();
+                      }
+                      public static abstract class PickConstructors.InputStream implements test.pkg.PickConstructors.Closeable {
+                        ctor public PickConstructors.InputStream();
+                      }
+                      public abstract class PickConstructors.ParcelFileDescriptor implements test.pkg.PickConstructors.Closeable {
+                        ctor public PickConstructors.ParcelFileDescriptor();
+                        method public abstract test.pkg.PickConstructors.FileDescriptor getFileDescriptor();
+                      }
+                    }
+                """,
+            source = """
+                    package test.pkg;
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public class PickConstructors {
+                    public PickConstructors() { throw new RuntimeException("Stub!"); }
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public abstract static class AutoCloseInputStream extends test.pkg.PickConstructors.FileInputStream {
+                    public AutoCloseInputStream(test.pkg.PickConstructors.ParcelFileDescriptor pfd) { super((test.pkg.PickConstructors.FileDescriptor)null); throw new RuntimeException("Stub!"); }
+                    }
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public abstract static class AutoCloseInputStream2 extends test.pkg.PickConstructors.FileInputStream {
+                    public AutoCloseInputStream2(test.pkg.PickConstructors.ParcelFileDescriptor pfd) { super((test.pkg.PickConstructors.FileDescriptor)null); throw new RuntimeException("Stub!"); }
+                    }
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public static interface AutoCloseable {
+                    }
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public static interface Closeable extends test.pkg.PickConstructors.AutoCloseable {
+                    }
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public static class File {
+                    public File() { throw new RuntimeException("Stub!"); }
+                    }
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public static final class FileDescriptor {
+                    public FileDescriptor() { throw new RuntimeException("Stub!"); }
+                    }
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public abstract static class FileInputStream extends test.pkg.PickConstructors.InputStream {
+                    public FileInputStream(java.lang.String name) throws test.pkg.PickConstructors.FileNotFoundException { throw new RuntimeException("Stub!"); }
+                    public FileInputStream(test.pkg.PickConstructors.File file) throws test.pkg.PickConstructors.FileNotFoundException { throw new RuntimeException("Stub!"); }
+                    public FileInputStream(test.pkg.PickConstructors.FileDescriptor fdObj) { throw new RuntimeException("Stub!"); }
+                    }
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public static class FileNotFoundException extends test.pkg.PickConstructors.IOException {
+                    public FileNotFoundException() { throw new RuntimeException("Stub!"); }
+                    }
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public static class IOException extends java.lang.Exception {
+                    public IOException() { throw new RuntimeException("Stub!"); }
+                    }
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public abstract static class InputStream implements test.pkg.PickConstructors.Closeable {
+                    public InputStream() { throw new RuntimeException("Stub!"); }
+                    }
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public abstract class ParcelFileDescriptor implements test.pkg.PickConstructors.Closeable {
+                    public ParcelFileDescriptor() { throw new RuntimeException("Stub!"); }
+                    public abstract test.pkg.PickConstructors.FileDescriptor getFileDescriptor();
+                    }
+                    }
+                    """
+        )
+    }
+
+    @Test
+    fun `Picking Constructors`() {
+        checkStubs(
+            checkDoclava1 = false,
+            sourceFiles =
+            *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+
+                    @SuppressWarnings({"WeakerAccess", "unused"})
+                    public class Constructors2 {
+                        public class TestSuite implements Test {
+
+                            public TestSuite() {
+                            }
+
+                            public TestSuite(final Class<?> theClass) {
+                            }
+
+                            public TestSuite(Class<? extends TestCase> theClass, String name) {
+                                this(theClass);
+                            }
+
+                            public TestSuite(String name) {
+                            }
+                            public TestSuite(Class<?>... classes) {
+                            }
+
+                            public TestSuite(Class<? extends TestCase>[] classes, String name) {
+                                this(classes);
+                            }
+                        }
+
+                        public class TestCase {
+                        }
+
+                        public interface Test {
+                        }
+
+                        public class Parent {
+                            public Parent(int x) throws IOException {
+                            }
+                        }
+
+                        class Intermediate extends Parent {
+                            Intermediate(int x) throws IOException { super(x); }
+                        }
+
+                        public class Child extends Intermediate {
+                            public Child() throws IOException { super(5); }
+                            public Child(float x) throws IOException { this(); }
+                        }
+
+                        // ----------------------------------------------------
+
+                        public abstract class DrawableWrapper {
+                            public DrawableWrapper(Drawable dr) {
+                            }
+
+                            DrawableWrapper(Clipstate state, Object resources) {
+                            }
+                        }
+
+
+                        public class ClipDrawable extends DrawableWrapper {
+                            ClipDrawable() {
+                                this(null);
+                            }
+
+                            public ClipDrawable(Drawable drawable, int gravity, int orientation) { this(null); }
+
+                            private ClipDrawable(Clipstate clipstate) {
+                                super(clipstate, null);
+                            }
+                        }
+
+                        public class Drawable {
+                        }
+
+                        class Clipstate {
+                        }
+                    }
+                    """
+                )
+            ),
+            warnings = "",
+            source = """
+                    package test.pkg;
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public class Constructors2 {
+                    public Constructors2() { throw new RuntimeException("Stub!"); }
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public class Child extends test.pkg.Constructors2.Parent {
+                    public Child() { super(0); throw new RuntimeException("Stub!"); }
+                    public Child(float x) { super(0); throw new RuntimeException("Stub!"); }
+                    }
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public class ClipDrawable extends test.pkg.Constructors2.DrawableWrapper {
+                    public ClipDrawable(test.pkg.Constructors2.Drawable drawable, int gravity, int orientation) { super(null); throw new RuntimeException("Stub!"); }
+                    }
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public class Drawable {
+                    public Drawable() { throw new RuntimeException("Stub!"); }
+                    }
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public abstract class DrawableWrapper {
+                    public DrawableWrapper(test.pkg.Constructors2.Drawable dr) { throw new RuntimeException("Stub!"); }
+                    }
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public class Parent {
+                    public Parent(int x) { throw new RuntimeException("Stub!"); }
+                    }
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public static interface Test {
+                    }
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public class TestCase {
+                    public TestCase() { throw new RuntimeException("Stub!"); }
+                    }
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public class TestSuite implements test.pkg.Constructors2.Test {
+                    public TestSuite() { throw new RuntimeException("Stub!"); }
+                    public TestSuite(java.lang.Class<?> theClass) { throw new RuntimeException("Stub!"); }
+                    public TestSuite(java.lang.Class<? extends test.pkg.Constructors2.TestCase> theClass, java.lang.String name) { throw new RuntimeException("Stub!"); }
+                    public TestSuite(java.lang.String name) { throw new RuntimeException("Stub!"); }
+                    public TestSuite(java.lang.Class<?>... classes) { throw new RuntimeException("Stub!"); }
+                    public TestSuite(java.lang.Class<? extends test.pkg.Constructors2.TestCase>[] classes, java.lang.String name) { throw new RuntimeException("Stub!"); }
+                    }
+                    }
+                    """
+        )
+    }
+
+    @Test
+    fun `Another Constructor Test`() {
+        // A specific scenario triggered in the API where the right super class detector was not chosen
+        checkStubs(
+            checkDoclava1 = false,
+            sourceFiles =
+            *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+
+                    @SuppressWarnings({"RedundantThrows", "JavaDoc", "WeakerAccess"})
+                    public class PickConstructors2 {
+                        public interface EventListener {
+                        }
+
+                        public interface PropertyChangeListener extends EventListener {
+                        }
+
+                        public static abstract class EventListenerProxy<T extends EventListener>
+                                implements EventListener {
+                            public EventListenerProxy(T listener) {
+                            }
+                        }
+
+                        public static class PropertyChangeListenerProxy
+                                extends EventListenerProxy<PropertyChangeListener>
+                                implements PropertyChangeListener {
+                            public PropertyChangeListenerProxy(String propertyName, PropertyChangeListener listener) {
+                                super(listener);
+                            }
+                        }
+                    }
+                    """
+                )
+            ),
+            warnings = "",
+            source = """
+                    package test.pkg;
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public class PickConstructors2 {
+                    public PickConstructors2() { throw new RuntimeException("Stub!"); }
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public static interface EventListener {
+                    }
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public abstract static class EventListenerProxy<T extends test.pkg.PickConstructors2.EventListener> implements test.pkg.PickConstructors2.EventListener {
+                    public EventListenerProxy(T listener) { throw new RuntimeException("Stub!"); }
+                    }
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public static interface PropertyChangeListener extends test.pkg.PickConstructors2.EventListener {
+                    }
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public static class PropertyChangeListenerProxy extends test.pkg.PickConstructors2.EventListenerProxy<test.pkg.PickConstructors2.PropertyChangeListener> implements test.pkg.PickConstructors2.PropertyChangeListener {
+                    public PropertyChangeListenerProxy(java.lang.String propertyName, test.pkg.PickConstructors2.PropertyChangeListener listener) { super(null); throw new RuntimeException("Stub!"); }
+                    }
+                    }
+                    """
+        )
+    }
+
+    @Test
+    fun `Overriding protected methods`() {
+        // Checks a scenario where the stubs were missing overrides
+        checkStubs(
+            checkDoclava1 = false,
+            sourceFiles =
+            *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+
+                    @SuppressWarnings("all")
+                    public class Layouts {
+                        public static class View {
+                            protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+                            }
+                        }
+
+                        public static abstract class ViewGroup extends View {
+                            @Override
+                            protected abstract void onLayout(boolean changed,
+                                    int l, int t, int r, int b);
+                        }
+
+                        public static class Toolbar extends ViewGroup {
+                            @Override
+                            protected void onLayout(boolean changed, int l, int t, int r, int b) {
+                            }
+                        }
+                    }
+                    """
+                )
+            ),
+            warnings = "",
+            api = """
+                    package test.pkg {
+                      public class Layouts {
+                        ctor public Layouts();
+                      }
+                      public static class Layouts.Toolbar extends test.pkg.Layouts.ViewGroup {
+                        ctor public Layouts.Toolbar();
+                      }
+                      public static class Layouts.View {
+                        ctor public Layouts.View();
+                        method protected void onLayout(boolean, int, int, int, int);
+                      }
+                      public static abstract class Layouts.ViewGroup extends test.pkg.Layouts.View {
+                        ctor public Layouts.ViewGroup();
+                        method protected abstract void onLayout(boolean, int, int, int, int);
+                      }
+                    }
+                    """,
+            source = """
+                    package test.pkg;
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public class Layouts {
+                    public Layouts() { throw new RuntimeException("Stub!"); }
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public static class Toolbar extends test.pkg.Layouts.ViewGroup {
+                    public Toolbar() { throw new RuntimeException("Stub!"); }
+                    protected void onLayout(boolean changed, int l, int t, int r, int b) { throw new RuntimeException("Stub!"); }
+                    }
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public static class View {
+                    public View() { throw new RuntimeException("Stub!"); }
+                    protected void onLayout(boolean changed, int left, int top, int right, int bottom) { throw new RuntimeException("Stub!"); }
+                    }
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public abstract static class ViewGroup extends test.pkg.Layouts.View {
+                    public ViewGroup() { throw new RuntimeException("Stub!"); }
+                    protected abstract void onLayout(boolean changed, int l, int t, int r, int b);
+                    }
+                    }
+                    """
+        )
+    }
+
+    @Test
+    fun `Missing overridden method`() {
+        // Another special case where overridden methods were missing
+        checkStubs(
+            checkDoclava1 = false,
+            sourceFiles =
+            *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+
+                    import java.util.Collection;
+                    import java.util.Set;
+
+                    @SuppressWarnings("all")
+                    public class SpanTest {
+                        public interface CharSequence {
+                        }
+                        public interface Spanned extends CharSequence {
+                            public int nextSpanTransition(int start, int limit, Class type);
+                        }
+
+                        public interface Spannable extends Spanned {
+                        }
+
+                        public class SpannableString extends SpannableStringInternal implements CharSequence, Spannable {
+                        }
+
+                        /* package */ abstract class SpannableStringInternal {
+                            public int nextSpanTransition(int start, int limit, Class kind) {
+                                return 0;
+                            }
+                        }
+                    }
+                    """
+                )
+            ),
+            warnings = "",
+            source = """
+                    package test.pkg;
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public class SpanTest {
+                    public SpanTest() { throw new RuntimeException("Stub!"); }
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public static interface CharSequence {
+                    }
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public static interface Spannable extends test.pkg.SpanTest.Spanned {
+                    }
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public class SpannableString implements test.pkg.SpanTest.CharSequence, test.pkg.SpanTest.Spannable {
+                    public SpannableString() { throw new RuntimeException("Stub!"); }
+                    // Inlined stub from hidden parent class test.pkg.SpanTest.SpannableStringInternal
+                    public int nextSpanTransition(int start, int limit, java.lang.Class kind) { throw new RuntimeException("Stub!"); }
+                    }
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public static interface Spanned extends test.pkg.SpanTest.CharSequence {
+                    public int nextSpanTransition(int start, int limit, java.lang.Class type);
+                    }
+                    }
+                    """
+        )
+    }
+
+    @Test
+    fun `Skip type variables in casts`() {
+        // When generating casts in super constructor calls, use raw types
+        checkStubs(
+            checkDoclava1 = false,
+            sourceFiles =
+            *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+
+                    @SuppressWarnings("all")
+                    public class Properties {
+                        public abstract class Property<T, V> {
+                            public Property(Class<V> type, String name) {
+                            }
+                            public Property(Class<V> type, String name, String name2) { // force casts in super
+                            }
+                        }
+
+                        public abstract class IntProperty<T> extends Property<T, Integer> {
+
+                            public IntProperty(String name) {
+                                super(Integer.class, name);
+                            }
+                        }
+                    }
+                    """
+                )
+            ),
+            warnings = "",
+            source = """
+                    package test.pkg;
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public class Properties {
+                    public Properties() { throw new RuntimeException("Stub!"); }
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public abstract class IntProperty<T> extends test.pkg.Properties.Property<T,java.lang.Integer> {
+                    public IntProperty(java.lang.String name) { super((java.lang.Class)null, (java.lang.String)null); throw new RuntimeException("Stub!"); }
+                    }
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    public abstract class Property<T, V> {
+                    public Property(java.lang.Class<V> type, java.lang.String name) { throw new RuntimeException("Stub!"); }
+                    public Property(java.lang.Class<V> type, java.lang.String name, java.lang.String name2) { throw new RuntimeException("Stub!"); }
+                    }
+                    }
+                    """
+        )
+    }
+
+    @Test
+    fun `Rewrite relative documentation links`() {
+        // When generating casts in super constructor calls, use raw types
+        checkStubs(
+            checkDoclava1 = false,
+            sourceFiles =
+            *arrayOf(
+                java(
+                    """
+                    package test.pkg1;
+                    import java.io.IOException;
+                    import test.pkg2.OtherClass;
+
+                    /**
+                     *  Blah blah {@link OtherClass} blah blah.
+                     *  Referencing <b>field</b> {@link OtherClass#foo},
+                     *  and referencing method {@link OtherClass#bar(int,
+                     *   boolean)}.
+                     *  And relative method reference {@link #baz()}.
+                     *  And relative field reference {@link #importance}.
+                     *  Here's an already fully qualified reference: {@link test.pkg2.OtherClass}.
+                     *  And here's one in the same package: {@link LocalClass}.
+                     *
+                     *  @deprecated For some reason
+                     *  @see OtherClass
+                     *  @see OtherClass#bar(int, boolean)
+                     */
+                    @SuppressWarnings("all")
+                    public class SomeClass {
+                       /**
+                       * My method.
+                       * @throws IOException when blah blah blah
+                       * @throws {@link RuntimeException} when blah blah blah
+                       */
+                       public void baz() throws IOException;
+                       public boolean importance;
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg2;
+
+                    @SuppressWarnings("all")
+                    public class OtherClass {
+                        public int foo;
+                        public void bar(int baz, boolean bar);
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg1;
+
+                    @SuppressWarnings("all")
+                    public class LocalClass {
+                    }
+                    """
+                )
+            ),
+            warnings = "",
+            source = """
+                    package test.pkg1;
+                    /**
+                     *  Blah blah {@link test.pkg2.OtherClass OtherClass} blah blah.
+                     *  Referencing <b>field</b> {@link test.pkg2.OtherClass#foo OtherClass#foo},
+                     *  and referencing method {@link test.pkg2.OtherClass#bar(int,
+                     *   boolean) OtherClass#bar(int,
+                     *   boolean)}.
+                     *  And relative method reference {@link #baz()}.
+                     *  And relative field reference {@link #importance}.
+                     *  Here's an already fully qualified reference: {@link test.pkg2.OtherClass}.
+                     *  And here's one in the same package: {@link LocalClass}.
+                     *
+                     *  @deprecated For some reason
+                     *  @see test.pkg2.OtherClass
+                     *  @see OtherClass#bar(int, boolean)
+                     */
+                    @SuppressWarnings({"unchecked", "deprecation", "all"})
+                    @Deprecated public class SomeClass {
+                    public SomeClass() { throw new RuntimeException("Stub!"); }
+                    /**
+                     * My method.
+                     * @throws java.io.IOException when blah blah blah
+                     * @throws {java.lang.RuntimeExceptionk RuntimeException} when blah blah blah
+                     */
+                    public void baz() throws java.io.IOException { throw new RuntimeException("Stub!"); }
+                    public boolean importance;
+                    }
+                    """
+        )
+    }
+
+    // TODO: Add in some type variables in method signatures and constructors!
+    // TODO: Test what happens when a class extends a hidden extends a public in separate packages,
+    // and the hidden has a @hide constructor so the stub in the leaf class doesn't compile -- I should
+    // check for this and fail build.
+
+    // TODO: Test -stubPackages
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/metalava/SystemServiceCheckTest.kt b/src/test/java/com/android/tools/metalava/SystemServiceCheckTest.kt
new file mode 100644
index 0000000..8966c98
--- /dev/null
+++ b/src/test/java/com/android/tools/metalava/SystemServiceCheckTest.kt
@@ -0,0 +1,365 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava
+
+import org.junit.Test
+
+class SystemServiceCheckTest : DriverTest() {
+    @Test
+    fun `SystemService OK, loaded from signature file`() {
+        check(
+            warnings = "", // OK
+            compatibilityMode = false,
+            includeSystemApiAnnotations = true,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    import android.annotation.RequiresPermission;
+                    @android.annotation.SystemService
+                    public class MyTest2 {
+                        @RequiresPermission(anyOf={"foo.bar.PERMISSION1","foo.bar.PERMISSION2"})
+                        public int myMethod1() { }
+                    }
+                    """
+                ),
+                systemServiceSource,
+                requiresPermissionSource
+            ),
+            manifest = """<?xml version="1.0" encoding="UTF-8"?>
+                <manifest xmlns:android="http://schemas.android.com/apk/res/android">
+                    <permission
+                        android:name="foo.bar.PERMISSION1"
+                        android:label="@string/foo"
+                        android:description="@string/foo"
+                        android:protectionLevel="signature"/>
+                    <permission
+                        android:name="foo.bar.PERMISSION2"
+                        android:protectionLevel="signature"/>
+
+                </manifest>
+                """
+        )
+    }
+
+    @Test
+    fun `SystemService OK, loaded from source`() {
+        check(
+            warnings = "", // OK
+            compatibilityMode = false,
+            includeSystemApiAnnotations = true,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+
+                    @android.annotation.SystemService
+                    public class MyTest2 {
+                        @android.annotation.RequiresPermission(anyOf={"foo.bar.PERMISSION1","foo.bar.PERMISSION2"})
+                        public void myMethod1() {
+                        }
+                    }
+                    """
+                ),
+                systemServiceSource
+            ),
+            manifest = """<?xml version="1.0" encoding="UTF-8"?>
+                <manifest xmlns:android="http://schemas.android.com/apk/res/android">
+                    <permission
+                        android:name="foo.bar.PERMISSION1"
+                        android:label="@string/foo"
+                        android:description="@string/foo"
+                        android:protectionLevel="signature"/>
+                    <permission
+                        android:name="foo.bar.PERMISSION2"
+                        android:protectionLevel="signature"/>
+
+                </manifest>
+                """
+        )
+    }
+
+    @Test
+    fun `Check SystemService -- no permission annotation`() {
+        check(
+            warnings = "src/test/pkg/MyTest1.java:1: lint: Method 'myMethod2' must be protected with a system permission. [RequiresPermission:125]",
+            compatibilityMode = false,
+            includeSystemApiAnnotations = true,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    @android.annotation.SystemService
+                    public class MyTest1 {
+                        public int myMethod2() { }
+                    }
+                    """
+                ),
+                systemServiceSource,
+                requiresPermissionSource
+            ),
+            manifest = """<?xml version="1.0" encoding="UTF-8"?>
+                <manifest/>
+                """
+        )
+    }
+
+    @Test
+    fun `Check SystemService -- can miss a permission with anyOf`() {
+        check(
+            warnings = "",
+            compatibilityMode = false,
+            includeSystemApiAnnotations = true,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    import android.annotation.RequiresPermission;
+                    @android.annotation.SystemService
+                    public class MyTest2 {
+                        @RequiresPermission(anyOf={"foo.bar.PERMISSION1","foo.bar.PERMISSION2"})
+                        public int myMethod1() { }
+                    }
+                    """
+                ),
+                systemServiceSource,
+                requiresPermissionSource
+            ),
+
+            manifest = """<?xml version="1.0" encoding="UTF-8"?>
+                <manifest xmlns:android="http://schemas.android.com/apk/res/android">
+                    <permission
+                        android:name="foo.bar.PERMISSION1"
+                        android:label="@string/foo"
+                        android:description="@string/foo"
+                        android:protectionLevel="signature"/>
+                </manifest>
+                """
+        )
+    }
+
+    @Test
+    fun `Check SystemService -- at least one permission must be defined with anyOf`() {
+        check(
+            warnings = """
+                    src/test/pkg/MyTest2.java:2: lint: Method 'myMethod1' must be protected with a system permission. [RequiresPermission:125]
+                    src/test/pkg/MyTest2.java:2: warning: None of the permissions foo.bar.PERMISSION1, foo.bar.PERMISSION2 are defined by manifest TESTROOT/manifest.xml. [RemovedField:10]
+                    """,
+            compatibilityMode = false,
+            includeSystemApiAnnotations = true,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    import android.annotation.RequiresPermission;
+                    @android.annotation.SystemService
+                    public class MyTest2 {
+                        @RequiresPermission(anyOf={"foo.bar.PERMISSION1","foo.bar.PERMISSION2"})
+                        public int myMethod1() { }
+                    }
+                    """
+                ),
+                systemServiceSource,
+                requiresPermissionSource
+            ),
+
+            manifest = """<?xml version="1.0" encoding="UTF-8"?>
+                <manifest/>
+                """
+        )
+    }
+
+    @Test
+    fun `Check SystemService -- missing one permission with allOf`() {
+        check(
+            warnings = "src/test/pkg/MyTest2.java:2: warning: Permission 'foo.bar.PERMISSION2' is not defined by manifest TESTROOT/manifest.xml. [RemovedField:10]",
+            compatibilityMode = false,
+            includeSystemApiAnnotations = true,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                        package test.pkg;
+                        import android.annotation.RequiresPermission;
+                        @android.annotation.SystemService
+                        public class MyTest2 {
+                            @RequiresPermission(allOf={"foo.bar.PERMISSION1","foo.bar.PERMISSION2"})
+                            public int test() { }
+                        }
+                        """
+                ),
+                systemServiceSource,
+                requiresPermissionSource
+            ),
+
+            manifest = """<?xml version="1.0" encoding="UTF-8"?>
+                <manifest xmlns:android="http://schemas.android.com/apk/res/android">
+                    <permission
+                        android:name="foo.bar.PERMISSION1"
+                        android:label="@string/foo"
+                        android:description="@string/foo"
+                        android:protectionLevel="signature"/>
+                </manifest>
+                """
+        )
+    }
+
+    @Test
+    fun `Check SystemService -- must be system permission, not normal`() {
+        check(
+            warnings = "src/test/pkg/MyTest2.java:2: lint: Method 'test' must be protected with a system " +
+                    "permission; it currently allows non-system callers holding [foo.bar.PERMISSION1, " +
+                    "foo.bar.PERMISSION2] [RequiresPermission:125]",
+            compatibilityMode = false,
+            includeSystemApiAnnotations = true,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    import android.annotation.RequiresPermission;
+                    @SuppressWarnings("WeakerAccess")
+                    @android.annotation.SystemService
+                    public class MyTest2 {
+                        @RequiresPermission(anyOf={"foo.bar.PERMISSION1","foo.bar.PERMISSION2"})
+                        public int test() { }
+                    }
+                    """
+                ),
+                systemServiceSource,
+                requiresPermissionSource
+            ),
+
+            manifest = """<?xml version="1.0" encoding="UTF-8"?>
+                    <manifest xmlns:android="http://schemas.android.com/apk/res/android">
+                        <permission
+                            android:name="foo.bar.PERMISSION1"
+                            android:label="@string/foo"
+                            android:description="@string/foo"
+                            android:protectionLevel="normal"/>
+                        <permission
+                            android:name="foo.bar.PERMISSION2"
+                            android:protectionLevel="normal"/>
+
+                    </manifest>
+                """
+        )
+    }
+
+    @Test
+    fun `Check SystemService -- missing manifest permissions`() {
+        check(
+            warnings = """
+                src/test/pkg/MyTest2.java:2: lint: Method 'test' must be protected with a system permission. [RequiresPermission:125]
+                src/test/pkg/MyTest2.java:2: warning: Permission 'Manifest.permission.MY_PERMISSION' is not defined by manifest TESTROOT/manifest.xml. [RemovedField:10]
+                src/test/pkg/MyTest2.java:2: warning: Permission 'Manifest.permission.MY_PERMISSION2' is not defined by manifest TESTROOT/manifest.xml. [RemovedField:10]
+                """,
+            compatibilityMode = false,
+            includeSystemApiAnnotations = true,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    import android.annotation.RequiresPermission;
+                    @android.annotation.SystemService
+                    public class MyTest2 {
+                        @RequiresPermission(allOf={Manifest.permission.MY_PERMISSION,Manifest.permission.MY_PERMISSION2})
+                        public int test() { }
+                    }
+                    """
+                ),
+                systemServiceSource,
+                requiresPermissionSource
+            ),
+            manifest = """<?xml version="1.0" encoding="UTF-8"?>
+                <manifest/>
+                """
+        )
+    }
+
+    @Test
+    fun `Invalid manifest`() {
+        check(
+            warnings = """
+                TESTROOT/manifest.xml: error: Failed to parse TESTROOT/manifest.xml: The markup in the document preceding the root element must be well-formed. [ParseError:1]
+                src/test/pkg/MyTest2.java:2: lint: Method 'test' must be protected with a system permission. [RequiresPermission:125]
+                src/test/pkg/MyTest2.java:2: warning: None of the permissions foo.bar.PERMISSION1, foo.bar.PERMISSION2 are defined by manifest TESTROOT/manifest.xml. [RemovedField:10]
+                """,
+            compatibilityMode = false,
+            includeSystemApiAnnotations = true,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+                    import android.annotation.RequiresPermission;
+                    @SuppressWarnings("WeakerAccess")
+                    @android.annotation.SystemService
+                    public class MyTest2 {
+                        @RequiresPermission(anyOf={"foo.bar.PERMISSION1","foo.bar.PERMISSION2"})
+                        public int test() { }
+                    }
+                    """
+                ),
+                systemServiceSource,
+                requiresPermissionSource
+            ),
+            manifest = """<?xml version="1.0" encoding="UTF-8"?>
+                </error>
+                """
+        )
+    }
+
+    @Test
+    fun `Warning suppressed via annotation`() {
+        check(
+            warnings = "", // OK (suppressed)
+            compatibilityMode = false,
+            includeSystemApiAnnotations = true,
+            sourceFiles = *arrayOf(
+                java(
+                    """
+                    package test.pkg;
+
+                    @android.annotation.SystemService
+                    public class MyTest1 {
+                        @android.annotation.SuppressLint({"RemovedField","RequiresPermission"})
+                        @android.annotation.RequiresPermission(anyOf={"foo.bar.PERMISSION1","foo.bar.PERMISSION2"})
+                        public void myMethod1() {
+                        }
+                    }
+                    """
+                ),
+                java(
+                    """
+                    package test.pkg;
+
+                    @android.annotation.SystemService
+                    public class MyTest2 {
+                        // Old suppress syntax
+                        @android.annotation.SuppressLint({"Doclava10","Doclava125"})
+                        @android.annotation.RequiresPermission(anyOf={"foo.bar.PERMISSION1","foo.bar.PERMISSION2"})
+                        public void myMethod1() {
+                        }
+                    }
+                    """
+                ),
+                systemServiceSource
+            ),
+            manifest = """<?xml version="1.0" encoding="UTF-8"?>
+                <manifest/>
+                            """
+        )
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/metalava/apilevels/ApiGeneratorTest.kt b/src/test/java/com/android/tools/metalava/apilevels/ApiGeneratorTest.kt
new file mode 100644
index 0000000..778408e
--- /dev/null
+++ b/src/test/java/com/android/tools/metalava/apilevels/ApiGeneratorTest.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava.apilevels
+
+import com.android.tools.metalava.DriverTest
+import com.android.utils.XmlUtils
+import com.google.common.truth.Truth
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import java.io.File
+import kotlin.text.Charsets.UTF_8
+
+class ApiGeneratorTest : DriverTest() {
+    @Test
+    fun `Extract API levels`() {
+        val oldSdkJars = File("prebuilts/tools/common/api-versions")
+        if (!oldSdkJars.isDirectory) {
+            println("Ignoring ${ApiGeneratorTest::class.java}: prebuilts not found - is \$PWD set to an Android source tree?")
+            return
+        }
+
+        val platformJars = File("prebuilts/sdk")
+        if (!platformJars.isDirectory) {
+            println("Ignoring ${ApiGeneratorTest::class.java}: prebuilts not found: $platformJars")
+            return
+        }
+
+        val output = File.createTempFile("api-info", "xml")
+        output.deleteOnExit()
+        val outputPath = output.path
+
+        check(
+            extraArguments = arrayOf(
+                "--generate-api-levels",
+                outputPath,
+                "--android-jar-pattern",
+                "${oldSdkJars.path}/android-%/android.jar",
+                "--android-jar-pattern",
+                "${platformJars.path}/%/android.jar"
+            ),
+            checkDoclava1 = false,
+            signatureSource = """
+                package test.pkg {
+                  public class MyTest {
+                    ctor public MyTest();
+                    method public int clamp(int);
+                    method public java.lang.Double convert(java.lang.Float);
+                    field public java.lang.Number myNumber;
+                  }
+                }
+                """
+        )
+
+        assertTrue(output.isFile)
+
+        val xml = output.readText(UTF_8)
+        Truth.assertThat(xml).contains("<class name=\"android/Manifest\$permission\" since=\"1\">")
+        Truth.assertThat(xml)
+            .contains("<field name=\"BIND_CARRIER_MESSAGING_SERVICE\" since=\"22\" deprecated=\"23\"/>")
+
+        val document = XmlUtils.parseDocumentSilently(xml, false)
+        assertNotNull(document)
+
+    }
+
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/metalava/model/TextBackedAnnotationItemTest.kt b/src/test/java/com/android/tools/metalava/model/TextBackedAnnotationItemTest.kt
new file mode 100644
index 0000000..cc4e121
--- /dev/null
+++ b/src/test/java/com/android/tools/metalava/model/TextBackedAnnotationItemTest.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava.model
+
+import com.android.tools.metalava.model.text.TextBackedAnnotationItem
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import java.util.function.Predicate
+
+class TextBackedAnnotationItemTest {
+    // Dummy for use in test where we don't need codebase functionality
+    private val dummyCodebase = object : DefaultCodebase() {
+        override fun supportsDocumentation(): Boolean = false
+        override var description: String = ""
+        override fun getPackages(): PackageList = unsupported()
+        override fun size(): Int = unsupported()
+        override fun findClass(className: String): ClassItem? = unsupported()
+        override fun findPackage(pkgName: String): PackageItem? = unsupported()
+        override fun trustedApi(): Boolean = false
+        override fun filter(filterEmit: Predicate<Item>, filterReference: Predicate<Item>): Codebase = unsupported()
+        override var supportsStagedNullability: Boolean = false
+    }
+
+    @Test
+    fun testSimple() {
+        val annotation = TextBackedAnnotationItem(
+            dummyCodebase,
+            "@android.support.annotation.Nullable"
+        )
+        assertEquals("@android.support.annotation.Nullable", annotation.toSource())
+        assertEquals("android.support.annotation.Nullable", annotation.qualifiedName())
+        assertTrue(annotation.attributes().isEmpty())
+    }
+
+    @Test
+    fun testIntRange() {
+        val annotation = TextBackedAnnotationItem(
+            dummyCodebase,
+            "@android.support.annotation.IntRange(from = 20, to = 40)"
+        )
+        assertEquals("@android.support.annotation.IntRange(from = 20, to = 40)", annotation.toSource())
+        assertEquals("android.support.annotation.IntRange", annotation.qualifiedName())
+        assertEquals(2, annotation.attributes().size)
+        assertEquals("from", annotation.findAttribute("from")?.name)
+        assertEquals("20", annotation.findAttribute("from")?.value.toString())
+        assertEquals("to", annotation.findAttribute("to")?.name)
+        assertEquals("40", annotation.findAttribute("to")?.value.toString())
+    }
+
+    @Test
+    fun testIntDef() {
+        val annotation = TextBackedAnnotationItem(
+            dummyCodebase,
+            "@android.support.annotation.IntDef({STYLE_NORMAL, STYLE_NO_TITLE, STYLE_NO_FRAME, STYLE_NO_INPUT})"
+        )
+        assertEquals(
+            "@android.support.annotation.IntDef({STYLE_NORMAL, STYLE_NO_TITLE, STYLE_NO_FRAME, STYLE_NO_INPUT})",
+            annotation.toSource()
+        )
+        assertEquals("android.support.annotation.IntDef", annotation.qualifiedName())
+        assertEquals(1, annotation.attributes().size)
+        val attribute = annotation.findAttribute("value")
+        assertNotNull(attribute)
+        assertEquals("value", attribute?.name)
+        assertEquals(
+            "{STYLE_NORMAL, STYLE_NO_TITLE, STYLE_NO_FRAME, STYLE_NO_INPUT}",
+            annotation.findAttribute("value")?.value.toString()
+        )
+
+        assertTrue(attribute?.value is AnnotationArrayAttributeValue)
+        if (attribute is AnnotationArrayAttributeValue) {
+            val list = attribute.values
+            assertEquals(3, list.size)
+            assertEquals("STYLE_NO_TITLE", list[1].toSource())
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/metalava/model/TypeItemTest.kt b/src/test/java/com/android/tools/metalava/model/TypeItemTest.kt
new file mode 100644
index 0000000..29c919f
--- /dev/null
+++ b/src/test/java/com/android/tools/metalava/model/TypeItemTest.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava.model
+
+import com.android.tools.metalava.options
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+class TypeItemTest {
+    @Test
+    fun test() {
+        options.omitCommonPackages = true
+        assertThat(TypeItem.shortenTypes("@android.support.annotation.Nullable")).isEqualTo("@Nullable")
+        assertThat(TypeItem.shortenTypes("java.lang.String")).isEqualTo("String")
+        assertThat(TypeItem.shortenTypes("java.lang.reflect.Method")).isEqualTo("java.lang.reflect.Method")
+        assertThat(TypeItem.shortenTypes("java.util.List<java.lang.String>")).isEqualTo("java.util.List<String>")
+        assertThat(TypeItem.shortenTypes("java.util.List<@android.support.annotation.NonNull java.lang.String>")).isEqualTo(
+            "java.util.List<@NonNull String>"
+        )
+    }
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/metalava/model/text/TextTypeItemTest.kt b/src/test/java/com/android/tools/metalava/model/text/TextTypeItemTest.kt
new file mode 100644
index 0000000..fe7e33d
--- /dev/null
+++ b/src/test/java/com/android/tools/metalava/model/text/TextTypeItemTest.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * 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.
+ */
+
+package com.android.tools.metalava.model.text
+
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+
+class TextTypeItemTest {
+    @Test
+    fun testTypeString() {
+        val full =
+            "@android.support.annotation.Nullable java.util.List<@android.support.annotation.Nullable java.lang.String>"
+        assertThat(TextTypeItem.toTypeString(full, false, false, false)).isEqualTo(
+            "java.util.List<java.lang.String>"
+        )
+        assertThat(TextTypeItem.toTypeString(full, false, true, false)).isEqualTo(
+            "java.util.List<@android.support.annotation.Nullable java.lang.String>"
+        )
+        assertThat(TextTypeItem.toTypeString(full, false, false, true)).isEqualTo(
+            "java.util.List"
+        )
+        assertThat(
+            TextTypeItem.toTypeString(
+                full,
+                true,
+                true,
+                false
+            )
+        ).isEqualTo("@android.support.annotation.Nullable java.util.List<@android.support.annotation.Nullable java.lang.String>")
+        assertThat(
+            TextTypeItem.toTypeString(
+                full,
+                true,
+                true,
+                true
+            )
+        ).isEqualTo("@android.support.annotation.Nullable java.util.List")
+        assertThat(TextTypeItem.toTypeString("int", false, false, false)).isEqualTo("int")
+    }
+}
\ No newline at end of file
diff --git a/stub-annotations/build.gradle b/stub-annotations/build.gradle
new file mode 100644
index 0000000..9a6cedc
--- /dev/null
+++ b/stub-annotations/build.gradle
@@ -0,0 +1,4 @@
+apply plugin: 'java-library'
+
+sourceCompatibility = "1.8"
+targetCompatibility = "1.8"
diff --git a/stub-annotations/src/main/java/android/support/annotation/Migrate.java b/stub-annotations/src/main/java/android/support/annotation/Migrate.java
new file mode 100644
index 0000000..233ce52
--- /dev/null
+++ b/stub-annotations/src/main/java/android/support/annotation/Migrate.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+package android.support.annotation;
+
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.RetentionPolicy.CLASS;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@Documented
+@Retention(CLASS)
+@Target({ANNOTATION_TYPE})
+public @interface Migrate {
+}
diff --git a/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java b/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
new file mode 100644
index 0000000..83f4e5a
--- /dev/null
+++ b/stub-annotations/src/main/java/android/support/annotation/NewlyNonNull.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * 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.
+ */
+package android.support.annotation;
+
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PACKAGE;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.CLASS;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@Documented
+@Retention(CLASS)
+@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE, ANNOTATION_TYPE, PACKAGE})
+@Migrate
+public @interface NewlyNonNull {
+}
diff --git a/stub-annotations/src/main/java/android/support/annotation/NewlyNullable.java b/stub-annotations/src/main/java/android/support/annotation/NewlyNullable.java
new file mode 100644
index 0000000..e11fe4f
--- /dev/null
+++ b/stub-annotations/src/main/java/android/support/annotation/NewlyNullable.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+package android.support.annotation;
+
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PACKAGE;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.CLASS;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@Documented
+@Retention(CLASS)
+@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE, ANNOTATION_TYPE, PACKAGE})
+@Migrate
+public @interface NewlyNullable {
+}
diff --git a/stub-annotations/src/main/java/android/support/annotation/RecentlyNonNull.java b/stub-annotations/src/main/java/android/support/annotation/RecentlyNonNull.java
new file mode 100644
index 0000000..d2309fc
--- /dev/null
+++ b/stub-annotations/src/main/java/android/support/annotation/RecentlyNonNull.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+package android.support.annotation;
+
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PACKAGE;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.CLASS;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@Documented
+@Retention(CLASS)
+@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE, ANNOTATION_TYPE, PACKAGE})
+@Migrate
+public @interface RecentlyNonNull {
+}
diff --git a/stub-annotations/src/main/java/android/support/annotation/RecentlyNullable.java b/stub-annotations/src/main/java/android/support/annotation/RecentlyNullable.java
new file mode 100644
index 0000000..d24bad0
--- /dev/null
+++ b/stub-annotations/src/main/java/android/support/annotation/RecentlyNullable.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * 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.
+ */
+package android.support.annotation;
+
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PACKAGE;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.CLASS;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@Documented
+@Retention(CLASS)
+@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE, ANNOTATION_TYPE, PACKAGE})
+@Migrate
+public @interface RecentlyNullable {
+}
-- 
GitLab