Repository: JakeWharton/butterknife Branch: master Commit: fcdebedf3276 Files: 232 Total size: 674.9 KB Directory structure: gitextract_9jjk0dbd/ ├── .buildscript/ │ └── deploy_snapshot.sh ├── .github/ │ └── workflows/ │ └── gradle-wrapper-validation.yml ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE.txt ├── README.md ├── RELEASING.md ├── build.gradle ├── butterknife/ │ ├── build.gradle │ ├── gradle.properties │ ├── proguard-rules.txt │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── butterknife/ │ │ └── ButterKnifeTest.java │ └── main/ │ ├── AndroidManifest.xml │ └── java/ │ └── butterknife/ │ ├── ButterKnife.java │ └── package-info.java ├── butterknife-annotations/ │ ├── build.gradle │ ├── gradle.properties │ └── src/ │ └── main/ │ └── java/ │ └── butterknife/ │ ├── BindAnim.java │ ├── BindArray.java │ ├── BindBitmap.java │ ├── BindBool.java │ ├── BindColor.java │ ├── BindDimen.java │ ├── BindDrawable.java │ ├── BindFloat.java │ ├── BindFont.java │ ├── BindInt.java │ ├── BindString.java │ ├── BindView.java │ ├── BindViews.java │ ├── OnCheckedChanged.java │ ├── OnClick.java │ ├── OnEditorAction.java │ ├── OnFocusChange.java │ ├── OnItemClick.java │ ├── OnItemLongClick.java │ ├── OnItemSelected.java │ ├── OnLongClick.java │ ├── OnPageChange.java │ ├── OnTextChanged.java │ ├── OnTouch.java │ ├── Optional.java │ └── internal/ │ ├── Constants.java │ ├── ListenerClass.java │ └── ListenerMethod.java ├── butterknife-compiler/ │ ├── build.gradle │ ├── gradle.properties │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── butterknife/ │ │ └── compiler/ │ │ ├── BindingSet.java │ │ ├── ButterKnifeProcessor.java │ │ ├── FieldAnimationBinding.java │ │ ├── FieldCollectionViewBinding.java │ │ ├── FieldDrawableBinding.java │ │ ├── FieldResourceBinding.java │ │ ├── FieldTypefaceBinding.java │ │ ├── FieldViewBinding.java │ │ ├── Id.java │ │ ├── MemberViewBinding.java │ │ ├── MethodViewBinding.java │ │ ├── Parameter.java │ │ ├── ResourceBinding.java │ │ └── ViewBinding.java │ └── test/ │ └── java/ │ └── butterknife/ │ └── compiler/ │ └── BindingSetTest.java ├── butterknife-gradle-plugin/ │ ├── build.gradle │ ├── gradle.properties │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── butterknife/ │ │ │ └── plugin/ │ │ │ ├── ButterKnifePlugin.kt │ │ │ ├── FinalRClassBuilder.kt │ │ │ ├── R2Generator.kt │ │ │ └── ResourceSymbolListReader.kt │ │ └── resources/ │ │ └── META-INF/ │ │ └── gradle-plugins/ │ │ └── com.jakewharton.butterknife.properties │ └── test/ │ ├── AndroidManifest.xml │ ├── build.gradle │ ├── fixtures/ │ │ └── suffix_parsed_properly/ │ │ └── src/ │ │ └── main/ │ │ ├── java/ │ │ │ └── butterknife/ │ │ │ └── test/ │ │ │ └── ButteryActivity.java │ │ └── res/ │ │ └── layout/ │ │ └── activity_layout.xml │ ├── java/ │ │ └── butterknife/ │ │ └── plugin/ │ │ ├── AndroidHome.kt │ │ ├── BuildFilesRule.kt │ │ ├── FinalRClassBuilderTest.kt │ │ └── FixturesTest.kt │ └── resources/ │ └── fixtures/ │ ├── R.txt │ └── R2.java ├── butterknife-integration-test/ │ ├── build.gradle │ └── src/ │ ├── androidTest/ │ │ ├── font_licenses.txt │ │ ├── java/ │ │ │ └── com/ │ │ │ └── example/ │ │ │ └── butterknife/ │ │ │ ├── functional/ │ │ │ │ ├── BindAnimTest.java │ │ │ │ ├── BindArrayTest.java │ │ │ │ ├── BindBitmapTest.java │ │ │ │ ├── BindBoolTest.java │ │ │ │ ├── BindColorTest.java │ │ │ │ ├── BindDimenTest.java │ │ │ │ ├── BindDrawableTest.java │ │ │ │ ├── BindFloatTest.java │ │ │ │ ├── BindFontTest.java │ │ │ │ ├── BindIntTest.java │ │ │ │ ├── BindStringTest.java │ │ │ │ ├── BindViewTest.java │ │ │ │ ├── BindViewsTest.java │ │ │ │ ├── OnCheckedChangedTest.java │ │ │ │ ├── OnClickTest.java │ │ │ │ ├── OnItemClickTest.java │ │ │ │ ├── OnItemLongClickTest.java │ │ │ │ ├── OnItemSelectedTest.java │ │ │ │ ├── OnLongClickTest.java │ │ │ │ ├── OnTouchTest.java │ │ │ │ └── ViewTree.java │ │ │ ├── library/ │ │ │ │ ├── SimpleActivityTest.java │ │ │ │ └── SimpleAdapterTest.java │ │ │ └── unbinder/ │ │ │ └── UnbinderTest.java │ │ ├── proguard.pro │ │ └── res/ │ │ ├── color/ │ │ │ └── colors.xml │ │ ├── drawable/ │ │ │ └── circle.xml │ │ └── values/ │ │ └── values.xml │ ├── androidTestReflect/ │ │ └── java/ │ │ └── com/ │ │ └── example/ │ │ └── butterknife/ │ │ └── functional/ │ │ ├── BindAnimFailureTest.java │ │ ├── BindArrayFailureTest.java │ │ ├── BindBitmapFailureTest.java │ │ ├── BindBoolFailureTest.java │ │ ├── BindColorFailureTest.java │ │ ├── BindDimenFailureTest.java │ │ ├── BindDrawableFailureTest.java │ │ ├── BindFloatFailureTest.java │ │ ├── BindFontFailureTest.java │ │ ├── BindIntFailureTest.java │ │ ├── BindStringFailureTest.java │ │ ├── BindViewFailureTest.java │ │ └── BindViewsFailureTest.java │ └── main/ │ ├── AndroidManifest.xml │ ├── java/ │ │ └── com/ │ │ └── example/ │ │ └── butterknife/ │ │ ├── SimpleApp.java │ │ ├── library/ │ │ │ ├── SimpleActivity.java │ │ │ └── SimpleAdapter.java │ │ └── unbinder/ │ │ ├── A.java │ │ ├── B.java │ │ ├── C.java │ │ ├── D.java │ │ ├── E.java │ │ ├── F.java │ │ ├── G.java │ │ └── H.java │ ├── proguard.pro │ └── res/ │ ├── layout/ │ │ ├── simple_activity.xml │ │ └── simple_list_item.xml │ └── values/ │ └── strings.xml ├── butterknife-lint/ │ ├── build.gradle │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── butterknife/ │ │ └── lint/ │ │ ├── InvalidR2UsageDetector.java │ │ └── LintRegistry.java │ └── test/ │ └── java/ │ └── butterknife/ │ └── lint/ │ ├── InvalidR2UsageDetectorTest.java │ └── LintRegistryTest.java ├── butterknife-reflect/ │ ├── README.md │ ├── build.gradle │ ├── gradle.properties │ ├── proguard-rules.txt │ └── src/ │ └── main/ │ ├── AndroidManifest.xml │ └── java/ │ └── butterknife/ │ ├── ButterKnife.java │ ├── CompositeUnbinder.java │ ├── EmptyTextWatcher.java │ ├── FieldUnbinder.java │ └── ListenerUnbinder.java ├── butterknife-runtime/ │ ├── build.gradle │ ├── gradle.properties │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── butterknife/ │ │ ├── ViewCollectionsTest.java │ │ └── internal/ │ │ └── UtilsTest.java │ ├── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── butterknife/ │ │ ├── Action.java │ │ ├── Setter.java │ │ ├── Unbinder.java │ │ ├── ViewCollections.java │ │ └── internal/ │ │ ├── DebouncingOnClickListener.java │ │ ├── ImmutableList.java │ │ └── Utils.java │ └── test/ │ └── java/ │ └── butterknife/ │ ├── BindAnimTest.java │ ├── BindArrayTest.java │ ├── BindBitmapTest.java │ ├── BindBoolTest.java │ ├── BindColorTest.java │ ├── BindDimenTest.java │ ├── BindDrawableTest.java │ ├── BindFloatTest.java │ ├── BindFontTest.java │ ├── BindIntTest.java │ ├── BindStringTest.java │ ├── BindViewTest.java │ ├── BindViewsTest.java │ ├── ClasspathParentBindTest.java │ ├── ExtendActivityTest.java │ ├── ExtendDialogTest.java │ ├── ExtendViewTest.java │ ├── OnClickTest.java │ ├── OnEditorActionTest.java │ ├── OnFocusChangeTest.java │ ├── OnItemClickTest.java │ ├── OnItemLongClickTest.java │ ├── OnItemSelectedTest.java │ ├── OnPageChangeTest.java │ ├── OnTextChangedTest.java │ ├── OnTouchTest.java │ ├── RClassTest.java │ ├── TestGeneratingProcessor.java │ ├── TestStubs.java │ ├── UnbinderTest.java │ └── UtilsTest.java ├── checkstyle.xml ├── deploy_website.sh ├── gradle/ │ ├── gradle-mvn-push.gradle │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat ├── sample/ │ ├── app/ │ │ ├── build.gradle │ │ └── src/ │ │ └── main/ │ │ ├── AndroidManifest.xml │ │ └── java/ │ │ └── com/ │ │ └── example/ │ │ └── butterknife/ │ │ ├── SimpleApp.java │ │ └── unbinder/ │ │ ├── A.java │ │ ├── B.java │ │ ├── C.java │ │ ├── D.java │ │ ├── E.java │ │ ├── F.java │ │ ├── G.java │ │ └── H.java │ └── library/ │ ├── build.gradle │ └── src/ │ └── main/ │ ├── AndroidManifest.xml │ ├── java/ │ │ └── com/ │ │ └── example/ │ │ └── butterknife/ │ │ └── library/ │ │ ├── SimpleActivity.java │ │ └── SimpleAdapter.java │ └── res/ │ ├── layout/ │ │ ├── simple_activity.xml │ │ └── simple_list_item.xml │ └── values/ │ └── strings.xml ├── settings.gradle └── website/ ├── ide-eclipse.html ├── ide-idea.html ├── index.html └── static/ ├── app.css ├── butter_android.psd ├── logo.psd ├── prettify.css └── prettify.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .buildscript/deploy_snapshot.sh ================================================ #!/bin/bash # # Deploy a jar, source jar, and javadoc jar to Sonatype's snapshot repo. # # Adapted from https://coderwall.com/p/9b_lfq and # http://benlimmer.com/2013/12/26/automatically-publish-javadoc-to-gh-pages-with-travis-ci/ SLUG="JakeWharton/butterknife" JDK="oraclejdk8" BRANCH="master" set -e if [ "$TRAVIS_REPO_SLUG" != "$SLUG" ]; then echo "Skipping snapshot deployment: wrong repository. Expected '$SLUG' but was '$TRAVIS_REPO_SLUG'." elif [ "$TRAVIS_JDK_VERSION" != "$JDK" ]; then echo "Skipping snapshot deployment: wrong JDK. Expected '$JDK' but was '$TRAVIS_JDK_VERSION'." elif [ "$TRAVIS_PULL_REQUEST" != "false" ]; then echo "Skipping snapshot deployment: was pull request." elif [ "$TRAVIS_BRANCH" != "$BRANCH" ]; then echo "Skipping snapshot deployment: wrong branch. Expected '$BRANCH' but was '$TRAVIS_BRANCH'." else echo "Deploying snapshot..." ./gradlew uploadArchives echo "Snapshot deployed!" fi ================================================ FILE: .github/workflows/gradle-wrapper-validation.yml ================================================ name: "Validate Gradle Wrapper" on: [push, pull_request] jobs: validation: name: "Validation" runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: gradle/wrapper-validation-action@v1 ================================================ FILE: .gitignore ================================================ bin gen out lib .idea *.iml classes obj .DS_Store # Gradle .gradle jniLibs build local.properties reports ================================================ FILE: .travis.yml ================================================ language: android jdk: - oraclejdk8 before_install: # Install SDK license so Android Gradle plugin can install deps. - mkdir "$ANDROID_HOME/licenses" || true - echo "d56f5187479451eabf01fb78af6dfcb131a6481e" > "$ANDROID_HOME/licenses/android-sdk-license" - echo "24333f8a63b6825ea9c5514f83c2829b004d1fee" >> "$ANDROID_HOME/licenses/android-sdk-license" # Install the rest of tools (e.g., avdmanager) - sdkmanager tools # Install the system image - sdkmanager "system-images;android-18;default;armeabi-v7a" # Create and start emulator for the script. Meant to race the install task. - echo no | avdmanager create avd --force -n test -k "system-images;android-18;default;armeabi-v7a" - $ANDROID_HOME/emulator/emulator -avd test -no-audio -no-window & install: ./gradlew clean assemble assembleAndroidTest --stacktrace before_script: - android-wait-for-emulator - adb shell input keyevent 82 script: ./gradlew check connectedCheck --stacktrace after_success: - .buildscript/deploy_snapshot.sh env: global: - secure: "ESbreW4FNMPQhV1zbFb9iBvhFWFbVHecaig3Si3+4nrJCMn9x4nqB18ZcU+Aviw67WQNcuSH4I0Hl08uknl+kzE/xKEfPLmu28bptXRCSued49aL11i2aQmRj5nqP2yxkinhtRGOQxzIo56NmFt7sIcEXODM3D5a6q7s9tlvPfw=" - secure: "JWEeqx0CWBqAkjcREHUg3Ei8wxqp59HZag8EidSLwmekgPJQwipwuEeXMZyPCGJCP+4ijUirtS/hRApi37BW0LYdt+XR7dI1TSZ0HFLTLqSPfWfsUcjmGpmoqVUv8FLVhC+KA42YeEhqkEaCUW92gJeAyK8swxDqGHAPT/sfKRA=" branches: except: - gh-pages notifications: email: false sudo: false before_cache: - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock - rm -fr $HOME/.gradle/caches/*/plugin-resolution/ cache: directories: - $HOME/.gradle/caches/ - $HOME/.gradle/wrapper/ - $HOME/.android/build-cache ================================================ FILE: CHANGELOG.md ================================================ Change Log ========== Version 10.2.3 *(2020-08-12)* ----------------------------- Heads up: Development on this tool is winding down as [view binding](https://developer.android.com/topic/libraries/view-binding) is stable in AS/AGP 3.6+. * Fix: Support receiving `MotionEvent` in an `@OnTouch` callback when using 'butterknife-reflect'. Version 10.2.2 *(2020-08-03)* ----------------------------- Heads up: Development on this tool is winding down as [view binding](https://developer.android.com/topic/libraries/view-binding) is stable in AS/AGP 3.6+. * Fix: Views detached while processing click callbacks will no longer disable future clicks on other views. Version 10.2.1 *(2019-12-19)* ----------------------------- Heads up: Development on this tool is winding down as [view binding](https://developer.android.com/topic/libraries/view-binding) will be stable in AS/AGP 3.6. * New: Make R2-generating Gradle task cacheable by default. * Fix: R2 classes now generate their own unique values for entries. This ensures that the annotation processor can always do a reverse mapping from ID back to name and type. In AGP 3.6.0, the `R.txt` symbol table that was previously used as a source for values now uses 0 for every entry which required this change. * Fix: Lint check for R2 values now properly handles static imports for entries. Version 10.2.0 *(2019-09-12)* ----------------------------- * New: Support incremental annotation processing. * Fix: Detect generated superclass bindings across compilation units. * Fix: Avoid deprecated APIs from the Android Gradle plugin. As a result, the new minimum supported version of the Android Gradle plugin is 3.3. Version 10.1.0 *(2019-02-13)* ----------------------------- * New: Listeners which require return values (e.g., long click) can now be bound to methods returning `void`. The default value of `true` will be returned in this case. * New: Add support for `@OnTextChanged` and `@OnPageChange` to reflection backend. * Remove enforcement of required views in the reflection backend. Most `@Nullable` annotations do not have runtime retention so they can't be checked at runtime with reflection. Instead of forcing everyone to find a new annotation, this enforcement is now dropped. While this might lead to nulls in otherwise required view bindings, they'll either be unused or quickly cause a `NullPointerException`. Version 10.0.0 *(2019-01-03)* ----------------------------- * Equivalent to 9.0.0 but only supports AndroidX-enabled builds. * Removed APIs deprecated in 9.0.0. Version 9.0.0 *(2019-01-03)* ---------------------------- * New: Support for AndroidX. Requires `android.useAndroidX=true` in `gradle.properties` to generate AndroidX code. * New: A `butterknife-runtime` artifact has been extracted from `butterknife` which contains the APIs required for the generated code but does not contain the code to reflectively look up the generated code. This allows you to reference the generated code directly such that R8/ProGuard optimization can rename both the generated code and your classes. `ButterKnife.bind` and the consumer R8/ProGuard rules remain in the old `butterknife` artifact. * New: Experimental `butterknife-reflect` artifact eliminates the need to run the annotation processor for IDE builds. This artifact is binary compatible with `butterknife` so it can be interchanged depending on how your build is being invoked. See [its README](butterknife-reflect/README.md) for more information. Currently about 90% of functionality is covered. File bugs for anything that does not work. Note: This artifact requires Java 8. There's no good reason for this except to push the ecosystem to having this be a default. As of AGP 3.2 there is no reason not to do this. * New: Lint checks have been ported to UAST and now work on Kotlin code. * Helpers such as `apply` have been deprecated on `ButterKnife` and are now available on the `ViewCollections` class. * Add support for Android Gradle plugin 3.3 and newer where `R` is no longer generated as Java source. This has a side-effect of removing support for Android Gradle plugin 3.0.x (and older). * Use Java 8 bytecode for all artifacts as announced in RC1 release notes. * Fix: Allow `@BindFont` to work prior to API 26 using `ResourcesCompat`. * Fix: Update Android Gradle plugin to 3.1 or newer to fix binary incompatibilities. * Fix: Correct generated resource annotation names when running Turkish locale. * Fix: Use the application ID instead of the resource package for generating `R2`. * Cache the fact that a class hierarchy has no remaining bindings to prevent traversing the hierarchy multiple times. * Deprecated methods from 8.x have been removed. Version 9.0.0-rc3 *(2018-12-20)* -------------------------------- * Fix: Correct generated resource annotation names when running Turkish locale. * Cache the fact that a class hierarchy has no remaining bindings to prevent traversing the hierarchy multiple times. Version 9.0.0-rc2 *(2018-11-19)* -------------------------------- * Add support for Android Gradle plugin 3.3 and newer where `R` is no longer generated as Java source. This has a side-effect of removing support for Android Gradle plugin 3.0.x (and older). * Use Java 8 bytecode for all artifacts as announced in RC1 release notes. Version 9.0.0-rc1 *(2018-10-10)* -------------------------------- * New: Support for AndroidX. Requires `android.useAndroidX=true` in `gradle.properties` to generate AndroidX code. * New: A `butterknife-runtime` artifact has been extracted from `butterknife` which contains the APIs required for the generated code but does not contain the code to reflectively look up the generated code. This allows you to reference the generated code directly such that R8/ProGuard optimization can rename both the generated code and your classes. `ButterKnife.bind` and the consumer R8/ProGuard rules remain in the old `butterknife` artifact. * New: Experimental `butterknife-reflect` artifact eliminates the need to run the annotation processor for IDE builds. This artifact is binary compatible with `butterknife` so it can be interchanged depending on how your build is being invoked. See [its README](butterknife-reflect/README.md) for more information. Currently about 90% of functionality is covered. File bugs for anything that does not work. Note: This artifact requires Java 8. There's no good reason for this except to push the ecosystem to having this be a default. As of AGP 3.2 there is no reason not to do this. * New: Lint checks have been ported to UAST and now work on Kotlin code. * Fix: Allow `@BindFont` to work prior to API 26 using `ResourcesCompat`. * Fix: Update Android Gradle plugin to 3.1 or newer to fix binary incompatibilities. * Fix: Use the application ID instead of the resource package for generating `R2`. * Deprecated methods from 8.x have been removed. Note: The next release candidate will switch all artifacts to require Java 8 bytecode which will force your applications to enable Java 8 bytecode. As of AGP 3.2 there is no cost to this, and there is no reason to have it set any lower. Version 8.8.1 *(2017-08-09)* ---------------------------- * Fix: Properly emit casts for single-bound view subtypes when `butterknife.debuggable` is set to `false`. Version 8.8.0 *(2017-08-04)* ---------------------------- * New: Processor option `butterknife.debuggable` controls whether debug information is generated. When specified as `false`, checks for required views being non-null are elided and casts are no longer guarded with user-friendly error messages. This reduces the amount of generated code for release builds at the expense of less friendly exceptions when something breaks. * Deprecate the `findById` methods. Compile against API 26 and use the normal `findViewById` for the same functionality. * Fix: Correct `@BindFont` code generation on pre-API 26 builds to pass a `Context` (not a `Resources`) to `ResourceCompat`. Version 8.7.0 *(2017-07-07)* ---------------------------- * New: `@BindFont` annotation binds `Typeface` instances with an optional style. Requires support libraries 26.0.0-beta1 or newer. * New: `@BindAnim` annotation binds `Animation` instances. * New: Generate `R2` constants for animation, layout, menu, plurals, styles, and styleables. * Fix: Properly catch and re-throw type cast exceptions when method binding arguments do not match. Version 8.6.0 *(2017-05-16)* ---------------------------- * Plugin was ported to Kotlin and updated to support future Android Gradle plugin versions. * Fix: Properly handle multiple library modules using Butter Knife and defining the same ID. * Fix: Use the same classloader of the binding target to load the generated view binding class. Version 8.5.1 *(2017-01-24)* ---------------------------- * Fix: Tweak bundled ProGuard rules to only retain the two-argument constructor accessed via reflection. Version 8.5.0 *(2017-01-23)* ---------------------------- * Emit `@SuppressLint` when using `@OnTouch` to avoid a lint warning. * Migrate lint checks from Lombok AST to JetBrains PSI. * Annotations are no longer claimed by the processor. * Based on the minimum SDK version (as specified by `butterknife.minSdk` until http://b.android.com/187527 is released) the generated code now changes to use newer APIs when available. * Generated classes now include single-argument overloads for `View`, `Activity`, and `Dialog` subclasses. * Generated classes are no longer generic. * Minimum supported SDK is now 9. Version 8.4.0 *(2016-08-26)* ---------------------------- * New: `@BindFloat` annotation for dimensions whose format is of type 'float'. See the annotation for more information. * Generated constructors are now annotated with `@UiThread` and non-final, base classes `unbind()` methods are annotated with `@CallSuper`. Version 8.3.0 *(2016-08-23)* ---------------------------- * New: Support for Jack compiler in application projects. * Fix: Generate ~20% less code and ~40% less methods. * Fix: Allow `@BindView` to reference types which are generated by other annotation processors. * Experimental: The generated view binding class can now be used directly. This allows ProGuard shrinking, optimization, and obfuscation to work without any rules being needed. For a class `Test`, the binding class will be named `Test_ViewBinding`. Calling its constructor will bind the instance passed in, and the create object is also the implementation of `Unbinder` that can be used to unbind the views. Note: The API of this generated code is subject to backwards-incompatible changes until v9.0. Version 8.2.1 *(2016-07-11)* ---------------------------- * Fix: Do not emit `android.R` imports in generated code. * Fix: Ensure the processor does not crash when scanning for `R` classes. This can occur when used in a Kotlin project. Version 8.2.0 *(2016-07-10)* ---------------------------- * New: Support for library projects. Requires application of a Butter Knife Gradle plugin. See README for details. * New: Generated code now emits `R` references instead of raw integer IDs. * Fix: `@OnPageChange` listener binding now uses the 'add'/'remove' methods on `ViewPager` instead of 'set'. Version 8.1.0 *(2016-06-14)* ---------------------------- * New: Change the structure of generated view binders to optimize for performance and generated code. This should result in faster binding (not that it's slow) and a reduction of methods. * Fix: Call the correct method on `TextView` to unbind `@OnTextChanged` uses. * Fix: Properly handle package names which contain uppercase letters. Version 8.0.1 *(2016-04-27)* ---------------------------- * Fix: ProGuard rules now prevent obfuscation of only types which reference ButterKnife annotations. * Eliminate some of the generated machinery when referenced from `final` types. Version 8.0.0 *(2016-04-25)* ---------------------------- * `@Bind` becomes `@BindView` and `@BindViews` (one view and multiple views, respectively). * Calls to `bind` now return an `Unbinder` instance which can be used to `null` references. This replaces the `unbind` API and adds support for being able to clear listeners. * New: `@BindArray` binds `String`, `CharSequence`, and `int` arrays and `TypeArray` to fields. * New: `@BindBitmap` binds `Bitmap` instances from resources to fields. * `@BindDrawable` now supports a `tint` field which accepts a theme attribute. * The runtime and compiler are now split into two artifacts. ```groovy compile 'com.jakewharton:butterknife:8.0.0' apt 'com.jakewharton:butterknife-compiler:8.0.0' ``` * New: `apply` overloads which accept a single view and arrays of views. * ProGuard rules now ship inside of the library and are included automatically. * `@Optional` annotation is back to mark methods as being optional. Version 7.0.1 *(2015-06-30)* ---------------------------- * Fix: Correct `ClassCastException` which occurred when `@Nullable` array bindings had missing views. Version 7.0.0 *(2015-06-27)* ---------------------------- * `@Bind` replaces `@InjectView` and `@InjectViews`. * `ButterKnife.bind` and `ButterKnife.unbind` replaces `ButterKnife.inject` and `ButterKnife.reset`, respectively. * `@Optional` has been removed. Use `@Nullable` from the 'support-annotations' library, or any other annotation named "Nullable". * New: Resource binding annotations! * `@BindBool` binds an `R.bool` ID to a `boolean` field. * `@BindColor` binds an `R.color` ID to an `int` or `ColorStateList` field. * `@BindDimen` binds an `R.dimen` ID to an `int` (for pixel size) or `float` (for exact value) field. * `@BindDrawable` binds an `R.drawable` ID to a `Drawable` field. * `@BindInt` binds an `R.int` ID to an `int` field. * `@BindString` binds an `R.string` ID to a `String` field. * Fix: Missing views will be filtered out from list and array bindings. * Note: If you are using Proguard, the generated class name has changed from being suffixed with `$$ViewInjector` to `$$ViewBinder`. Version 6.1.0 *(2015-01-29)* ---------------------------- * New: Support for injecting interface types everywhere that views were previously supported (e.g., `Checkable`). * Eliminate reflection-based method invocation for injection and resetting. This makes performance slightly faster (although if you are worried about the performance of Butter Knife you have other problems). The only reflection in the library is a single `Class.forName` lookup for each type. Version 6.0.0 *(2014-10-27)* ---------------------------- * New: Listeners can bind to the root view being injected by omitting a view ID on the annotation. * New: Exceptions thrown from missing views now include the human-readable ID name (e.g., 'button1'). * Specifying multiple fields binding to the same ID is now considered an error. * `findById` overload for view lookup on `Dialog` instances. * Experimental: Click listeners are now globally debounced per frame. This means that only a single click will be processed per frame preventing race conditions due to queued input events. * Experimental: Multiple methods can bind to the same listener provided that listener's callback method does not require a return value. Version 5.1.2 *(2014-08-01)* ---------------------------- * Report an error if the annotations are on a class inside the `android.*` or `java.*` package. Since we ignore these packages in the runtime, injection would never work. Version 5.1.1 *(2014-06-19)* ---------------------------- * Fix: Correct rare `ClassCastException` when unwinding an `InvocationTargetException`. Version 5.1.0 *(2014-05-20)* ---------------------------- * New listener! * `View`: `@OnTouch`. * Fix: `@Optional` now correctly works for `@InjectViews` fields. * Fix: Correct erasure problem which may have prevented the processor from running in Eclipse. Version 5.0.1 *(2014-05-04)* ---------------------------- * New: Support `Dialog` as injection source. * Fix: Unwrap `InvocationTargetException` causes for more helpful exceptions. Version 5.0.0 *(2014-04-21)* ---------------------------- * New: `@InjectViews` annotation groups multiple IDs into a `List` or array. * New: `ButterKnife.apply` method applies an `Action`, `Setter`, or Android `Property` to views in a list. * New listeners! * `ViewPager`: `@OnPageChange`. * `AdapterView`: `@OnItemSelected`. * `TextView`: `@OnTextChanged`. * New: Multi-method listener support. Specify a `callback` argument to choose which method the binding is for. *(See `@OnItemSelected` for an example)* * Fix: Support for generic types which are declared with an upper-bound. * Fix: Use less sophisticated method injection inspection in the annotation processor. The previous method caused problems with some Eclipse configurations. Version 4.0.1 *(2013-11-25)* ---------------------------- * Fix: Correct a problem preventing the annotation processor to access Android types when certain `javac` configurations were used to build. Version 4.0.0 *(2013-11-25)* ---------------------------- `Views` class is now named `ButterKnife` * New listeners! * `View`: `@OnLongClick` and `@OnFocusChanged`. * `TextView`: `@OnEditorAction`. * `AdapterView`: `@OnItemClick` and `@OnItemLongClick`. * `CompoundButton`: `@OnCheckedChanged`. * New: Views are now only checked to be `null` once if at least one of the fields and/or methods lack the `@Optional` annotation. * Fix: Do no emit redundant casts to `View` for methods. Version 3.0.1 *(2013-11-12)* ---------------------------- * Fix: Do not emit redundant casts to `View`. Version 3.0.0 *(2013-09-10)* ---------------------------- * New: Injections are now required. An exception will be thrown if a view is not found. Add `@Optional` annotation to suppress this verification. Version 2.0.1 *(2013-07-18)* ---------------------------- * New: Control debug logging via `Views.setDebug`. Version 2.0.0 *(2013-07-16)* ---------------------------- * New: `@OnClick` annotation for binding click listeners to methods! Version 1.4.0 *(2013-06-03)* ---------------------------- * New: `Views.reset` for settings injections back to `null` in a fragment's `onDestroyView` callback. * Fix: Support parent class injection when the parent class has generics. Version 1.3.2 *(2013-04-27)* ---------------------------- * Multiple injections of the same view ID only require a single find call. * Fix: Ensure injection happens on classes who do not have any injections but their superclasses do. Version 1.3.1 *(2013-04-12)* ---------------------------- * Fix: Parent class inflater resolution now generates correct code. Version 1.3.0 *(2013-03-26)* ---------------------------- * New: Injection on objects that have zero `@InjectView`-annotated fields will no longer throw an exception. Version 1.2.2 *(2013-03-11)* ---------------------------- * Fix: Prevent annotations on private classes. Version 1.2.1 *(2013-03-11)* ---------------------------- * Fix: Correct generated code for parent class inflation. * Fix: Allow injection on `protected`-scoped fields. Version 1.2.0 *(2013-05-07)* ---------------------------- * Support injection on any object using an Activity as the view root. * Support injection on views for their children. * Fix: Annotation errors now appear on the affected field in IDEs. Version 1.1.1 *(2013-05-06)* ---------------------------- * Fix: Verify that the target type extends from `View`. * Fix: Correct package name resolution in Eclipse 4.2 Version 1.1.0 *(2013-03-05)* ---------------------------- * Perform injection on any object by passing a view root. * Fix: Correct naming of static inner-class injection points. * Fix: Enforce `findById` can only be used with child classes of `View`. Version 1.0.0 *(2013-03-05)* ---------------------------- Initial release. ================================================ FILE: LICENSE.txt ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] 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: README.md ================================================ Butter Knife ============ **Attention**: This tool is now deprecated. Please switch to [view binding](https://developer.android.com/topic/libraries/view-binding). Existing versions will continue to work, obviously, but only critical bug fixes for integration with AGP will be considered. Feature development and general bug fixes have stopped. ![Logo](website/static/logo.png) Field and method binding for Android views which uses annotation processing to generate boilerplate code for you. * Eliminate `findViewById` calls by using `@BindView` on fields. * Group multiple views in a list or array. Operate on all of them at once with actions, setters, or properties. * Eliminate anonymous inner-classes for listeners by annotating methods with `@OnClick` and others. * Eliminate resource lookups by using resource annotations on fields. ```java class ExampleActivity extends Activity { @BindView(R.id.user) EditText username; @BindView(R.id.pass) EditText password; @BindString(R.string.login_error) String loginErrorMessage; @OnClick(R.id.submit) void submit() { // TODO call server... } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.simple_activity); ButterKnife.bind(this); // TODO Use fields... } } ``` For documentation and additional information see [the website][3]. __Remember: A butter knife is like a [dagger][1], only infinitely less sharp.__ Download -------- ```groovy android { ... // Butterknife requires Java 8. compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } } dependencies { implementation 'com.jakewharton:butterknife:10.2.3' annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.3' } ``` If you are using Kotlin, replace `annotationProcessor` with `kapt`. Snapshots of the development version are available in [Sonatype's `snapshots` repository][snap]. Library projects -------------------- To use Butter Knife in a library, add the plugin to your `buildscript`: ```groovy buildscript { repositories { mavenCentral() google() } dependencies { classpath 'com.jakewharton:butterknife-gradle-plugin:10.2.3' } } ``` and then apply it in your module: ```groovy apply plugin: 'com.android.library' apply plugin: 'com.jakewharton.butterknife' ``` Now make sure you use `R2` instead of `R` inside all Butter Knife annotations. ```java class ExampleActivity extends Activity { @BindView(R2.id.user) EditText username; @BindView(R2.id.pass) EditText password; ... } ``` License ------- Copyright 2013 Jake Wharton 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. [1]: https://dagger.dev/ [2]: https://search.maven.org/remote_content?g=com.jakewharton&a=butterknife&v=LATEST [3]: http://jakewharton.github.com/butterknife/ [snap]: https://oss.sonatype.org/content/repositories/snapshots/ ================================================ FILE: RELEASING.md ================================================ Releasing ======== 1. Change the version in `gradle.properties` to a non-SNAPSHOT version. 2. Update the `CHANGELOG.md` for the impending release. 3. Update the `README.md` with the new version. 4. `git commit -am "Prepare for release X.Y.Z."` (where X.Y.Z is the new version) 5. `./gradlew clean uploadArchives`. 6. Visit [Sonatype Nexus](https://oss.sonatype.org/) and promote the artifact. 7. `git tag -a X.Y.X -m "Version X.Y.Z"` (where X.Y.Z is the new version) 8. Update the `gradle.properties` to the next SNAPSHOT version. 9. `git commit -am "Prepare next development version."` 10. `git push && git push --tags` 11. Update the two sample modules to point to the newly released version. If step 5 or 6 fails, drop the Sonatype repo, fix the problem, commit, and start again at step 5. ================================================ FILE: build.gradle ================================================ apply plugin: 'com.github.ben-manes.versions' buildscript { ext.versions = [ 'minSdk': 14, 'compileSdk': 28, 'androidTools': '26.2.0', 'kotlin': '1.2.71', 'incap' : '0.2', 'release': '8.8.1', ] ext.deps = [ android: [ 'runtime': 'com.google.android:android:4.1.1.4', 'gradlePlugin': "com.android.tools.build:gradle:3.3.0", ], 'androidx': [ 'core': "androidx.core:core:1.0.0", 'viewpager': "androidx.viewpager:viewpager:1.0.0", 'annotations': "androidx.annotation:annotation:1.0.0", 'test': [ 'runner': 'androidx.test:runner:1.1.0', 'rules': 'androidx.test:rules:1.1.0', ], ], 'lint': [ 'core': "com.android.tools.lint:lint:${versions.androidTools}", 'api': "com.android.tools.lint:lint-api:${versions.androidTools}", 'checks': "com.android.tools.lint:lint-checks:${versions.androidTools}", 'tests': "com.android.tools.lint:lint-tests:${versions.androidTools}", ], javapoet: 'com.squareup:javapoet:1.10.0', junit: 'junit:junit:4.12', truth: 'com.google.truth:truth:0.42', compiletesting: 'com.google.testing.compile:compile-testing:0.15', 'auto': [ 'service': 'com.google.auto.service:auto-service:1.0-rc4', 'common': 'com.google.auto:auto-common:0.10', ], 'guava': 'com.google.guava:guava:24.0-jre', 'release': [ 'runtime': "com.jakewharton:butterknife:${versions.release}", 'compiler': "com.jakewharton:butterknife-compiler:${versions.release}" ], 'kotlin': [ 'stdLibJdk8': "org.jetbrains.kotlin:kotlin-stdlib-jdk8:${versions.kotlin}", ], 'incap': [ 'runtime': "net.ltgt.gradle.incap:incap:${versions.incap}", 'processor': "net.ltgt.gradle.incap:incap-processor:${versions.incap}", ] ] repositories { mavenCentral() google() jcenter() gradlePluginPortal() } dependencies { classpath 'com.android.tools.build:gradle:3.2.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}" classpath 'com.github.ben-manes:gradle-versions-plugin:0.17.0' classpath 'net.ltgt.gradle:gradle-errorprone-plugin:0.0.16' } } subprojects { project -> group = GROUP version = VERSION_NAME repositories { mavenCentral() google() jcenter() } apply plugin: 'net.ltgt.errorprone' dependencies { errorprone 'com.google.errorprone:error_prone_core:2.3.1' } // TODO figure out why this causes codegen to fail in android tests. //def nullaway = dependencies.create('com.uber.nullaway:nullaway:0.5.5') //configurations.all { Configuration configuration -> // if (configuration.name.endsWith('nnotationProcessor')) { // configuration.dependencies.add(nullaway) // } //} // //tasks.withType(JavaCompile) { // options.compilerArgs += [ // '-Xep:NullAway:ERROR', // '-XepOpt:NullAway:AnnotatedPackages=butterknife', // ] //} if (!project.name.equals('butterknife-gradle-plugin')) { apply plugin: 'checkstyle' task checkstyle(type: Checkstyle) { configFile rootProject.file('checkstyle.xml') source 'src/main/java' ignoreFailures false showViolations true include '**/*.java' classpath = files() } afterEvaluate { if (project.tasks.findByName('check')) { check.dependsOn('checkstyle') } } } } ================================================ FILE: butterknife/build.gradle ================================================ apply plugin: 'com.android.library' android { compileSdkVersion versions.compileSdk defaultConfig { minSdkVersion versions.minSdk consumerProguardFiles 'proguard-rules.txt' testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } lintOptions { textReport true textOutput 'stdout' // We run a full lint analysis as build part in CI, so skip vital checks for assemble tasks. checkReleaseBuilds false } // TODO replace with https://issuetracker.google.com/issues/72050365 once released. libraryVariants.all { it.generateBuildConfig.enabled = false } } dependencies { api project(':butterknife-runtime') androidTestImplementation deps.junit androidTestImplementation deps.truth androidTestImplementation deps.androidx.test.runner androidTestAnnotationProcessor project(':butterknife-compiler') } apply from: rootProject.file('gradle/gradle-mvn-push.gradle') ================================================ FILE: butterknife/gradle.properties ================================================ POM_ARTIFACT_ID=butterknife POM_NAME=Butterknife POM_PACKAGING=aar ================================================ FILE: butterknife/proguard-rules.txt ================================================ # Retain generated class which implement Unbinder. -keep public class * implements butterknife.Unbinder { public (**, android.view.View); } # Prevent obfuscation of types which use ButterKnife annotations since the simple name # is used to reflectively look up the generated ViewBinding. -keep class butterknife.* -keepclasseswithmembernames class * { @butterknife.* ; } -keepclasseswithmembernames class * { @butterknife.* ; } ================================================ FILE: butterknife/src/androidTest/java/butterknife/ButterKnifeTest.java ================================================ package butterknife; import android.content.Context; import android.view.View; import androidx.test.InstrumentationRegistry; import org.junit.After; import org.junit.Before; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; public class ButterKnifeTest { private final Context context = InstrumentationRegistry.getContext(); @Before @After // Clear out cache of binders before and after each test. public void resetViewsCache() { ButterKnife.BINDINGS.clear(); } @Test public void zeroBindingsBindDoesNotThrowExceptionAndCaches() { class Example { } Example example = new Example(); View view = new View(context); assertThat(ButterKnife.BINDINGS).isEmpty(); assertThat(ButterKnife.bind(example, view)).isSameAs(Unbinder.EMPTY); assertThat(ButterKnife.BINDINGS).containsEntry(Example.class, null); } @Test public void bindingKnownPackagesIsNoOp() { View view = new View(context); ButterKnife.bind(view); assertThat(ButterKnife.BINDINGS).isEmpty(); ButterKnife.bind(new Object(), view); assertThat(ButterKnife.BINDINGS).isEmpty(); } } ================================================ FILE: butterknife/src/main/AndroidManifest.xml ================================================ ================================================ FILE: butterknife/src/main/java/butterknife/ButterKnife.java ================================================ package butterknife; import android.app.Activity; import android.app.Dialog; import android.util.Log; import android.view.View; import androidx.annotation.CheckResult; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.UiThread; import androidx.annotation.VisibleForTesting; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; /** * Field and method binding for Android views. Use this class to simplify finding views and * attaching listeners by binding them with annotations. *

* Finding views from your activity is as easy as: *


 * public class ExampleActivity extends Activity {
 *   {@literal @}BindView(R.id.title) EditText titleView;
 *   {@literal @}BindView(R.id.subtitle) EditText subtitleView;
 *
 *   {@literal @}Override protected void onCreate(Bundle savedInstanceState) {
 *     super.onCreate(savedInstanceState);
 *     setContentView(R.layout.example_activity);
 *     ButterKnife.bind(this);
 *   }
 * }
 * 
* Binding can be performed directly on an {@linkplain #bind(Activity) activity}, a * {@linkplain #bind(View) view}, or a {@linkplain #bind(Dialog) dialog}. Alternate objects to * bind can be specified along with an {@linkplain #bind(Object, Activity) activity}, * {@linkplain #bind(Object, View) view}, or * {@linkplain #bind(Object, android.app.Dialog) dialog}. *

* Group multiple views together into a {@link List} or array. *


 * {@literal @}BindView({R.id.first_name, R.id.middle_name, R.id.last_name})
 * List nameViews;
 * 
*

* To bind listeners to your views you can annotate your methods: *


 * {@literal @}OnClick(R.id.submit) void onSubmit() {
 *   // React to button click.
 * }
 * 
* Any number of parameters from the listener may be used on the method. *

 * {@literal @}OnItemClick(R.id.tweet_list) void onTweetClicked(int position) {
 *   // React to tweet click.
 * }
 * 
*

* Be default, views are required to be present in the layout for both field and method bindings. * If a view is optional add a {@code @Nullable} annotation for fields (such as the one in the * support-annotations library) * or the {@code @Optional} annotation for methods. *


 * {@literal @}Nullable @BindView(R.id.title) TextView subtitleView;
 * 
* Resources can also be bound to fields to simplify programmatically working with views: *

 * {@literal @}BindBool(R.bool.is_tablet) boolean isTablet;
 * {@literal @}BindInt(R.integer.columns) int columns;
 * {@literal @}BindColor(R.color.error_red) int errorRed;
 * 
*/ public final class ButterKnife { private ButterKnife() { throw new AssertionError("No instances."); } private static final String TAG = "ButterKnife"; private static boolean debug = false; @VisibleForTesting static final Map, Constructor> BINDINGS = new LinkedHashMap<>(); /** Control whether debug logging is enabled. */ public static void setDebug(boolean debug) { ButterKnife.debug = debug; } /** * BindView annotated fields and methods in the specified {@link Activity}. The current content * view is used as the view root. * * @param target Target activity for view binding. */ @NonNull @UiThread public static Unbinder bind(@NonNull Activity target) { View sourceView = target.getWindow().getDecorView(); return bind(target, sourceView); } /** * BindView annotated fields and methods in the specified {@link View}. The view and its children * are used as the view root. * * @param target Target view for view binding. */ @NonNull @UiThread public static Unbinder bind(@NonNull View target) { return bind(target, target); } /** * BindView annotated fields and methods in the specified {@link Dialog}. The current content * view is used as the view root. * * @param target Target dialog for view binding. */ @NonNull @UiThread public static Unbinder bind(@NonNull Dialog target) { View sourceView = target.getWindow().getDecorView(); return bind(target, sourceView); } /** * BindView annotated fields and methods in the specified {@code target} using the {@code source} * {@link Activity} as the view root. * * @param target Target class for view binding. * @param source Activity on which IDs will be looked up. */ @NonNull @UiThread public static Unbinder bind(@NonNull Object target, @NonNull Activity source) { View sourceView = source.getWindow().getDecorView(); return bind(target, sourceView); } /** * BindView annotated fields and methods in the specified {@code target} using the {@code source} * {@link Dialog} as the view root. * * @param target Target class for view binding. * @param source Dialog on which IDs will be looked up. */ @NonNull @UiThread public static Unbinder bind(@NonNull Object target, @NonNull Dialog source) { View sourceView = source.getWindow().getDecorView(); return bind(target, sourceView); } /** * BindView annotated fields and methods in the specified {@code target} using the {@code source} * {@link View} as the view root. * * @param target Target class for view binding. * @param source View root on which IDs will be looked up. */ @NonNull @UiThread public static Unbinder bind(@NonNull Object target, @NonNull View source) { Class targetClass = target.getClass(); if (debug) Log.d(TAG, "Looking up binding for " + targetClass.getName()); Constructor constructor = findBindingConstructorForClass(targetClass); if (constructor == null) { return Unbinder.EMPTY; } //noinspection TryWithIdenticalCatches Resolves to API 19+ only type. try { return constructor.newInstance(target, source); } catch (IllegalAccessException e) { throw new RuntimeException("Unable to invoke " + constructor, e); } catch (InstantiationException e) { throw new RuntimeException("Unable to invoke " + constructor, e); } catch (InvocationTargetException e) { Throwable cause = e.getCause(); if (cause instanceof RuntimeException) { throw (RuntimeException) cause; } if (cause instanceof Error) { throw (Error) cause; } throw new RuntimeException("Unable to create binding instance.", cause); } } @Nullable @CheckResult @UiThread private static Constructor findBindingConstructorForClass(Class cls) { Constructor bindingCtor = BINDINGS.get(cls); if (bindingCtor != null || BINDINGS.containsKey(cls)) { if (debug) Log.d(TAG, "HIT: Cached in binding map."); return bindingCtor; } String clsName = cls.getName(); if (clsName.startsWith("android.") || clsName.startsWith("java.") || clsName.startsWith("androidx.")) { if (debug) Log.d(TAG, "MISS: Reached framework class. Abandoning search."); return null; } try { Class bindingClass = cls.getClassLoader().loadClass(clsName + "_ViewBinding"); //noinspection unchecked bindingCtor = (Constructor) bindingClass.getConstructor(cls, View.class); if (debug) Log.d(TAG, "HIT: Loaded binding class and constructor."); } catch (ClassNotFoundException e) { if (debug) Log.d(TAG, "Not found. Trying superclass " + cls.getSuperclass().getName()); bindingCtor = findBindingConstructorForClass(cls.getSuperclass()); } catch (NoSuchMethodException e) { throw new RuntimeException("Unable to find binding constructor for " + clsName, e); } BINDINGS.put(cls, bindingCtor); return bindingCtor; } } ================================================ FILE: butterknife/src/main/java/butterknife/package-info.java ================================================ /** * Field and method binding for Android views which uses annotation processing to generate * boilerplate code for you. *

*

    *
  • Eliminate {@link android.view.View#findViewById findViewById} calls by using * {@link butterknife.BindView @BindView} on fields.
  • *
  • Group multiple views in a {@linkplain java.util.List list} or array. *
  • Eliminate anonymous inner-classes for listeners by annotating methods with * {@link butterknife.OnClick @OnClick} and others.
  • *
  • Eliminate resource lookups by using resource annotations on fields.
  • *
*/ package butterknife; ================================================ FILE: butterknife-annotations/build.gradle ================================================ apply plugin: 'java-library' apply plugin: 'checkstyle' sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 checkstyle { configFile rootProject.file('checkstyle.xml') showViolations true } dependencies { compileOnly deps.android.runtime api deps.androidx.annotations } apply from: rootProject.file('gradle/gradle-mvn-push.gradle') ================================================ FILE: butterknife-annotations/gradle.properties ================================================ POM_NAME=Butterknife Annotations POM_ARTIFACT_ID=butterknife-annotations POM_PACKAGING=jar ================================================ FILE: butterknife-annotations/src/main/java/butterknife/BindAnim.java ================================================ package butterknife; import androidx.annotation.AnimRes; import java.lang.annotation.Retention; import java.lang.annotation.Target; import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** * Bind a field to the specified animation resource ID. *

 * {@literal @}BindAnim(R.anim.fade_in) Animation fadeIn;
 * 
*/ @Target(FIELD) @Retention(RUNTIME) public @interface BindAnim { /** Animation resource ID to which the field will be bound. */ @AnimRes int value(); } ================================================ FILE: butterknife-annotations/src/main/java/butterknife/BindArray.java ================================================ package butterknife; import androidx.annotation.ArrayRes; import java.lang.annotation.Retention; import java.lang.annotation.Target; import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** * Bind a field to the specified array resource ID. The type of array will be inferred from the * annotated element. * * String array: *

 * {@literal @}BindArray(R.array.countries) String[] countries;
 * 
* * Int array: *

 * {@literal @}BindArray(R.array.phones) int[] phones;
 * 
* * Text array: *

 * {@literal @}BindArray(R.array.options) CharSequence[] options;
 * 
* * {@link android.content.res.TypedArray}: *

 * {@literal @}BindArray(R.array.icons) TypedArray icons;
 * 
*/ @Retention(RUNTIME) @Target(FIELD) public @interface BindArray { /** Array resource ID to which the field will be bound. */ @ArrayRes int value(); } ================================================ FILE: butterknife-annotations/src/main/java/butterknife/BindBitmap.java ================================================ package butterknife; import android.graphics.Bitmap; import androidx.annotation.DrawableRes; import java.lang.annotation.Retention; import java.lang.annotation.Target; import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** * Bind a field to a {@link Bitmap} from the specified drawable resource ID. *

 * {@literal @}BindBitmap(R.drawable.logo) Bitmap logo;
 * 
*/ @Target(FIELD) @Retention(RUNTIME) public @interface BindBitmap { /** Drawable resource ID from which the {@link Bitmap} will be created. */ @DrawableRes int value(); } ================================================ FILE: butterknife-annotations/src/main/java/butterknife/BindBool.java ================================================ package butterknife; import androidx.annotation.BoolRes; import java.lang.annotation.Retention; import java.lang.annotation.Target; import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** * Bind a field to the specified boolean resource ID. *

 * {@literal @}BindBool(R.bool.is_tablet) boolean isTablet;
 * 
*/ @Target(FIELD) @Retention(RUNTIME) public @interface BindBool { /** Boolean resource ID to which the field will be bound. */ @BoolRes int value(); } ================================================ FILE: butterknife-annotations/src/main/java/butterknife/BindColor.java ================================================ package butterknife; import androidx.annotation.ColorRes; import java.lang.annotation.Retention; import java.lang.annotation.Target; import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** * Bind a field to the specified color resource ID. Type can be {@code int} or * {@link android.content.res.ColorStateList}. *

 * {@literal @}BindColor(R.color.background_green) int green;
 * {@literal @}BindColor(R.color.background_green_selector) ColorStateList greenSelector;
 * 
*/ @Target(FIELD) @Retention(RUNTIME) public @interface BindColor { /** Color resource ID to which the field will be bound. */ @ColorRes int value(); } ================================================ FILE: butterknife-annotations/src/main/java/butterknife/BindDimen.java ================================================ package butterknife; import androidx.annotation.DimenRes; import java.lang.annotation.Retention; import java.lang.annotation.Target; import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** * Bind a field to the specified dimension resource ID. Type can be {@code int} for pixel size or * {@code float} for exact amount. *

 * {@literal @}BindDimen(R.dimen.horizontal_gap) int gapPx;
 * {@literal @}BindDimen(R.dimen.horizontal_gap) float gap;
 * 
*/ @Target(FIELD) @Retention(RUNTIME) public @interface BindDimen { /** Dimension resource ID to which the field will be bound. */ @DimenRes int value(); } ================================================ FILE: butterknife-annotations/src/main/java/butterknife/BindDrawable.java ================================================ package butterknife; import androidx.annotation.AttrRes; import androidx.annotation.DrawableRes; import java.lang.annotation.Retention; import java.lang.annotation.Target; import static butterknife.internal.Constants.NO_RES_ID; import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** * Bind a field to the specified drawable resource ID. *

 * {@literal @}BindDrawable(R.drawable.placeholder)
 * Drawable placeholder;
 * {@literal @}BindDrawable(value = R.drawable.placeholder, tint = R.attr.colorAccent)
 * Drawable tintedPlaceholder;
 * 
*/ @Target(FIELD) @Retention(RUNTIME) public @interface BindDrawable { /** Drawable resource ID to which the field will be bound. */ @DrawableRes int value(); /** Color attribute resource ID that is used to tint the drawable. */ @AttrRes int tint() default NO_RES_ID; } ================================================ FILE: butterknife-annotations/src/main/java/butterknife/BindFloat.java ================================================ package butterknife; import androidx.annotation.DimenRes; import java.lang.annotation.Retention; import java.lang.annotation.Target; import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** * Bind a field to the specified dimension resource ID whose type is explicitly defined as float. *

* This is different than simply reading a normal dimension as a float value which * {@link BindDimen @BindDimen} supports. The resource must be defined as a float like * {@code 1.1}. *


 * {@literal @}BindFloat(R.dimen.image_ratio) float imageRatio;
 * 
*/ @Target(FIELD) @Retention(RUNTIME) public @interface BindFloat { /** Float resource ID to which the field will be bound. */ @DimenRes int value(); } ================================================ FILE: butterknife-annotations/src/main/java/butterknife/BindFont.java ================================================ package butterknife; import android.graphics.Typeface; import androidx.annotation.FontRes; import androidx.annotation.IntDef; import androidx.annotation.RestrictTo; import java.lang.annotation.Retention; import java.lang.annotation.Target; import static androidx.annotation.RestrictTo.Scope.LIBRARY; import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** * Bind a field to the specified font resource ID. *

 * {@literal @}BindFont(R.font.comic_sans) Typeface comicSans;
 * 
*/ @Target(FIELD) @Retention(RUNTIME) public @interface BindFont { /** Font resource ID to which the field will be bound. */ @FontRes int value(); @TypefaceStyle int style() default Typeface.NORMAL; @IntDef({ Typeface.NORMAL, Typeface.BOLD, Typeface.ITALIC, Typeface.BOLD_ITALIC }) @RestrictTo(LIBRARY) @interface TypefaceStyle { } } ================================================ FILE: butterknife-annotations/src/main/java/butterknife/BindInt.java ================================================ package butterknife; import androidx.annotation.IntegerRes; import java.lang.annotation.Retention; import java.lang.annotation.Target; import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** * Bind a field to the specified integer resource ID. *

 * {@literal @}BindInt(R.int.columns) int columns;
 * 
*/ @Target(FIELD) @Retention(RUNTIME) public @interface BindInt { /** Integer resource ID to which the field will be bound. */ @IntegerRes int value(); } ================================================ FILE: butterknife-annotations/src/main/java/butterknife/BindString.java ================================================ package butterknife; import androidx.annotation.StringRes; import java.lang.annotation.Retention; import java.lang.annotation.Target; import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** * Bind a field to the specified string resource ID. *

 * {@literal @}BindString(R.string.username_error) String usernameErrorText;
 * 
*/ @Retention(RUNTIME) @Target(FIELD) public @interface BindString { /** String resource ID to which the field will be bound. */ @StringRes int value(); } ================================================ FILE: butterknife-annotations/src/main/java/butterknife/BindView.java ================================================ package butterknife; import androidx.annotation.IdRes; import java.lang.annotation.Retention; import java.lang.annotation.Target; import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** * Bind a field to the view for the specified ID. The view will automatically be cast to the field * type. *

 * {@literal @}BindView(R.id.title) TextView title;
 * 
*/ @Retention(RUNTIME) @Target(FIELD) public @interface BindView { /** View ID to which the field will be bound. */ @IdRes int value(); } ================================================ FILE: butterknife-annotations/src/main/java/butterknife/BindViews.java ================================================ package butterknife; import androidx.annotation.IdRes; import java.lang.annotation.Retention; import java.lang.annotation.Target; import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** * Bind a field to the view for the specified ID. The view will automatically be cast to the field * type. *

 * {@literal @}BindViews({ R.id.title, R.id.subtitle })
 * List<TextView> titles;
 * 
*/ @Retention(RUNTIME) @Target(FIELD) public @interface BindViews { /** View IDs to which the field will be bound. */ @IdRes int[] value(); } ================================================ FILE: butterknife-annotations/src/main/java/butterknife/OnCheckedChanged.java ================================================ package butterknife; import android.view.View; import androidx.annotation.IdRes; import butterknife.internal.ListenerClass; import butterknife.internal.ListenerMethod; import java.lang.annotation.Retention; import java.lang.annotation.Target; import static android.widget.CompoundButton.OnCheckedChangeListener; import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** * Bind a method to an {@link OnCheckedChangeListener OnCheckedChangeListener} on the view for * each ID specified. *

 * {@literal @}OnCheckedChanged(R.id.example) void onChecked(boolean checked) {
 *   Toast.makeText(this, checked ? "Checked!" : "Unchecked!", Toast.LENGTH_SHORT).show();
 * }
 * 
* Any number of parameters from * {@link OnCheckedChangeListener#onCheckedChanged(android.widget.CompoundButton, boolean) * onCheckedChanged} may be used on the method. * * @see OnCheckedChangeListener */ @Target(METHOD) @Retention(RUNTIME) @ListenerClass( targetType = "android.widget.CompoundButton", setter = "setOnCheckedChangeListener", type = "android.widget.CompoundButton.OnCheckedChangeListener", method = @ListenerMethod( name = "onCheckedChanged", parameters = { "android.widget.CompoundButton", "boolean" } ) ) public @interface OnCheckedChanged { /** View IDs to which the method will be bound. */ @IdRes int[] value() default { View.NO_ID }; } ================================================ FILE: butterknife-annotations/src/main/java/butterknife/OnClick.java ================================================ package butterknife; import android.view.View; import androidx.annotation.IdRes; import butterknife.internal.ListenerClass; import butterknife.internal.ListenerMethod; import java.lang.annotation.Retention; import java.lang.annotation.Target; import static android.view.View.OnClickListener; import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** * Bind a method to an {@link OnClickListener OnClickListener} on the view for each ID specified. *

 * {@literal @}OnClick(R.id.example) void onClick() {
 *   Toast.makeText(this, "Clicked!", Toast.LENGTH_SHORT).show();
 * }
 * 
* Any number of parameters from * {@link OnClickListener#onClick(android.view.View) onClick} may be used on the * method. * * @see OnClickListener */ @Target(METHOD) @Retention(RUNTIME) @ListenerClass( targetType = "android.view.View", setter = "setOnClickListener", type = "butterknife.internal.DebouncingOnClickListener", method = @ListenerMethod( name = "doClick", parameters = "android.view.View" ) ) public @interface OnClick { /** View IDs to which the method will be bound. */ @IdRes int[] value() default { View.NO_ID }; } ================================================ FILE: butterknife-annotations/src/main/java/butterknife/OnEditorAction.java ================================================ package butterknife; import android.view.View; import androidx.annotation.IdRes; import butterknife.internal.ListenerClass; import butterknife.internal.ListenerMethod; import java.lang.annotation.Retention; import java.lang.annotation.Target; import static android.widget.TextView.OnEditorActionListener; import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** * Bind a method to an {@link OnEditorActionListener OnEditorActionListener} on the view for each * ID specified. *

 * {@literal @}OnEditorAction(R.id.example) boolean onEditorAction(KeyEvent key) {
 *   Toast.makeText(this, "Pressed: " + key, Toast.LENGTH_SHORT).show();
 *   return true;
 * }
 * 
* Any number of parameters from * {@link OnEditorActionListener#onEditorAction(android.widget.TextView, int, android.view.KeyEvent) * onEditorAction} may be used on the method. *

* If the return type of the method is {@code void}, true will be returned from the listener. * * @see OnEditorActionListener */ @Target(METHOD) @Retention(RUNTIME) @ListenerClass( targetType = "android.widget.TextView", setter = "setOnEditorActionListener", type = "android.widget.TextView.OnEditorActionListener", method = @ListenerMethod( name = "onEditorAction", parameters = { "android.widget.TextView", "int", "android.view.KeyEvent" }, returnType = "boolean", defaultReturn = "true" ) ) public @interface OnEditorAction { /** View IDs to which the method will be bound. */ @IdRes int[] value() default { View.NO_ID }; } ================================================ FILE: butterknife-annotations/src/main/java/butterknife/OnFocusChange.java ================================================ package butterknife; import android.view.View; import androidx.annotation.IdRes; import butterknife.internal.ListenerClass; import butterknife.internal.ListenerMethod; import java.lang.annotation.Retention; import java.lang.annotation.Target; import static android.view.View.OnFocusChangeListener; import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** * Bind a method to an {@link OnFocusChangeListener OnFocusChangeListener} on the view for each ID * specified. *


 * {@literal @}OnFocusChange(R.id.example) void onFocusChanged(boolean focused) {
 *   Toast.makeText(this, focused ? "Gained focus" : "Lost focus", Toast.LENGTH_SHORT).show();
 * }
 * 
* Any number of parameters from {@link OnFocusChangeListener#onFocusChange(android.view.View, * boolean) onFocusChange} may be used on the method. * * @see OnFocusChangeListener */ @Target(METHOD) @Retention(RUNTIME) @ListenerClass( targetType = "android.view.View", setter = "setOnFocusChangeListener", type = "android.view.View.OnFocusChangeListener", method = @ListenerMethod( name = "onFocusChange", parameters = { "android.view.View", "boolean" } ) ) public @interface OnFocusChange { /** View IDs to which the method will be bound. */ @IdRes int[] value() default { View.NO_ID }; } ================================================ FILE: butterknife-annotations/src/main/java/butterknife/OnItemClick.java ================================================ package butterknife; import android.view.View; import androidx.annotation.IdRes; import butterknife.internal.ListenerClass; import butterknife.internal.ListenerMethod; import java.lang.annotation.Retention; import java.lang.annotation.Target; import static android.widget.AdapterView.OnItemClickListener; import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** * Bind a method to an {@link OnItemClickListener OnItemClickListener} on the view for each ID * specified. *

 * {@literal @}OnItemClick(R.id.example_list) void onItemClick(int position) {
 *   Toast.makeText(this, "Clicked position " + position + "!", Toast.LENGTH_SHORT).show();
 * }
 * 
* Any number of parameters from {@link OnItemClickListener#onItemClick(android.widget.AdapterView, * android.view.View, int, long) onItemClick} may be used on the method. * * @see OnItemClickListener */ @Target(METHOD) @Retention(RUNTIME) @ListenerClass( targetType = "android.widget.AdapterView", setter = "setOnItemClickListener", type = "android.widget.AdapterView.OnItemClickListener", method = @ListenerMethod( name = "onItemClick", parameters = { "android.widget.AdapterView", "android.view.View", "int", "long" } ) ) public @interface OnItemClick { /** View IDs to which the method will be bound. */ @IdRes int[] value() default { View.NO_ID }; } ================================================ FILE: butterknife-annotations/src/main/java/butterknife/OnItemLongClick.java ================================================ package butterknife; import android.view.View; import androidx.annotation.IdRes; import butterknife.internal.ListenerClass; import butterknife.internal.ListenerMethod; import java.lang.annotation.Retention; import java.lang.annotation.Target; import static android.widget.AdapterView.OnItemLongClickListener; import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** * Bind a method to an {@link OnItemLongClickListener OnItemLongClickListener} on the view for each * ID specified. *

 * {@literal @}OnItemLongClick(R.id.example_list) boolean onItemLongClick(int position) {
 *   Toast.makeText(this, "Long clicked position " + position + "!", Toast.LENGTH_SHORT).show();
 *   return true;
 * }
 * 
* Any number of parameters from * {@link OnItemLongClickListener#onItemLongClick(android.widget.AdapterView, android.view.View, * int, long) onItemLongClick} may be used on the method. *

* If the return type of the method is {@code void}, true will be returned from the listener. * * @see OnItemLongClickListener */ @Target(METHOD) @Retention(RUNTIME) @ListenerClass( targetType = "android.widget.AdapterView", setter = "setOnItemLongClickListener", type = "android.widget.AdapterView.OnItemLongClickListener", method = @ListenerMethod( name = "onItemLongClick", parameters = { "android.widget.AdapterView", "android.view.View", "int", "long" }, returnType = "boolean", defaultReturn = "true" ) ) public @interface OnItemLongClick { /** View IDs to which the method will be bound. */ @IdRes int[] value() default { View.NO_ID }; } ================================================ FILE: butterknife-annotations/src/main/java/butterknife/OnItemSelected.java ================================================ package butterknife; import android.view.View; import androidx.annotation.IdRes; import butterknife.internal.ListenerClass; import butterknife.internal.ListenerMethod; import java.lang.annotation.Retention; import java.lang.annotation.Target; import static android.widget.AdapterView.OnItemSelectedListener; import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.RetentionPolicy.CLASS; /** * Bind a method to an {@link OnItemSelectedListener OnItemSelectedListener} on the view for each * ID specified. *


 * {@literal @}OnItemSelected(R.id.example_list) void onItemSelected(int position) {
 *   Toast.makeText(this, "Selected position " + position + "!", Toast.LENGTH_SHORT).show();
 * }
 * 
* Any number of parameters from * {@link OnItemSelectedListener#onItemSelected(android.widget.AdapterView, android.view.View, int, * long) onItemSelected} may be used on the method. *

* To bind to methods other than {@code onItemSelected}, specify a different {@code callback}. *


 * {@literal @}OnItemSelected(value = R.id.example_list, callback = NOTHING_SELECTED)
 * void onNothingSelected() {
 *   Toast.makeText(this, "Nothing selected!", Toast.LENGTH_SHORT).show();
 * }
 * 
* * @see OnItemSelectedListener */ @Target(METHOD) @Retention(CLASS) @ListenerClass( targetType = "android.widget.AdapterView", setter = "setOnItemSelectedListener", type = "android.widget.AdapterView.OnItemSelectedListener", callbacks = OnItemSelected.Callback.class ) public @interface OnItemSelected { /** View IDs to which the method will be bound. */ @IdRes int[] value() default { View.NO_ID }; /** Listener callback to which the method will be bound. */ Callback callback() default Callback.ITEM_SELECTED; /** {@link OnItemSelectedListener} callback methods. */ enum Callback { /** * {@link OnItemSelectedListener#onItemSelected(android.widget.AdapterView, android.view.View, * int, long)} */ @ListenerMethod( name = "onItemSelected", parameters = { "android.widget.AdapterView", "android.view.View", "int", "long" } ) ITEM_SELECTED, /** {@link OnItemSelectedListener#onNothingSelected(android.widget.AdapterView)} */ @ListenerMethod( name = "onNothingSelected", parameters = "android.widget.AdapterView" ) NOTHING_SELECTED } } ================================================ FILE: butterknife-annotations/src/main/java/butterknife/OnLongClick.java ================================================ package butterknife; import android.view.View; import androidx.annotation.IdRes; import butterknife.internal.ListenerClass; import butterknife.internal.ListenerMethod; import java.lang.annotation.Retention; import java.lang.annotation.Target; import static android.view.View.OnLongClickListener; import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** * Bind a method to an {@link OnLongClickListener OnLongClickListener} on the view for each ID * specified. *

 * {@literal @}OnLongClick(R.id.example) boolean onLongClick() {
 *   Toast.makeText(this, "Long clicked!", Toast.LENGTH_SHORT).show();
 *   return true;
 * }
 * 
* Any number of parameters from {@link OnLongClickListener#onLongClick(android.view.View)} may be * used on the method. *

* If the return type of the method is {@code void}, true will be returned from the listener. * * @see OnLongClickListener */ @Target(METHOD) @Retention(RUNTIME) @ListenerClass( targetType = "android.view.View", setter = "setOnLongClickListener", type = "android.view.View.OnLongClickListener", method = @ListenerMethod( name = "onLongClick", parameters = { "android.view.View" }, returnType = "boolean", defaultReturn = "true" ) ) public @interface OnLongClick { /** View IDs to which the method will be bound. */ @IdRes int[] value() default { View.NO_ID }; } ================================================ FILE: butterknife-annotations/src/main/java/butterknife/OnPageChange.java ================================================ package butterknife; import android.view.View; import androidx.annotation.IdRes; import butterknife.internal.ListenerClass; import butterknife.internal.ListenerMethod; import java.lang.annotation.Retention; import java.lang.annotation.Target; import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** * Bind a method to an {@code OnPageChangeListener} on the view for each ID specified. *


 * {@literal @}OnPageChange(R.id.example_pager) void onPageSelected(int position) {
 *   Toast.makeText(this, "Selected " + position + "!", Toast.LENGTH_SHORT).show();
 * }
 * 
* Any number of parameters from {@code onPageSelected} may be used on the method. *

* To bind to methods other than {@code onPageSelected}, specify a different {@code callback}. *


 * {@literal @}OnPageChange(value = R.id.example_pager, callback = PAGE_SCROLL_STATE_CHANGED)
 * void onPageStateChanged(int state) {
 *   Toast.makeText(this, "State changed: " + state + "!", Toast.LENGTH_SHORT).show();
 * }
 * 
*/ @Target(METHOD) @Retention(RUNTIME) @ListenerClass( targetType = "androidx.viewpager.widget.ViewPager", setter = "addOnPageChangeListener", remover = "removeOnPageChangeListener", type = "androidx.viewpager.widget.ViewPager.OnPageChangeListener", callbacks = OnPageChange.Callback.class ) public @interface OnPageChange { /** View IDs to which the method will be bound. */ @IdRes int[] value() default { View.NO_ID }; /** Listener callback to which the method will be bound. */ Callback callback() default Callback.PAGE_SELECTED; /** {@code ViewPager.OnPageChangeListener} callback methods. */ enum Callback { /** {@code onPageSelected(int)} */ @ListenerMethod( name = "onPageSelected", parameters = "int" ) PAGE_SELECTED, /** {@code onPageScrolled(int, float, int)} */ @ListenerMethod( name = "onPageScrolled", parameters = { "int", "float", "int" } ) PAGE_SCROLLED, /** {@code onPageScrollStateChanged(int)} */ @ListenerMethod( name = "onPageScrollStateChanged", parameters = "int" ) PAGE_SCROLL_STATE_CHANGED, } } ================================================ FILE: butterknife-annotations/src/main/java/butterknife/OnTextChanged.java ================================================ package butterknife; import android.text.TextWatcher; import android.view.View; import androidx.annotation.IdRes; import butterknife.internal.ListenerClass; import butterknife.internal.ListenerMethod; import java.lang.annotation.Retention; import java.lang.annotation.Target; import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** * Bind a method to a {@link TextWatcher TextWatcher} on the view for each ID specified. *

 * {@literal @}OnTextChanged(R.id.example) void onTextChanged(CharSequence text) {
 *   Toast.makeText(this, "Text changed: " + text, Toast.LENGTH_SHORT).show();
 * }
 * 
* Any number of parameters from {@link TextWatcher#onTextChanged(CharSequence, int, int, int) * onTextChanged} may be used on the method. *

* To bind to methods other than {@code onTextChanged}, specify a different {@code callback}. *


 * {@literal @}OnTextChanged(value = R.id.example, callback = BEFORE_TEXT_CHANGED)
 * void onBeforeTextChanged(CharSequence text) {
 *   Toast.makeText(this, "Before text changed: " + text, Toast.LENGTH_SHORT).show();
 * }
 * 
* * @see TextWatcher */ @Target(METHOD) @Retention(RUNTIME) @ListenerClass( targetType = "android.widget.TextView", setter = "addTextChangedListener", remover = "removeTextChangedListener", type = "android.text.TextWatcher", callbacks = OnTextChanged.Callback.class ) public @interface OnTextChanged { /** View IDs to which the method will be bound. */ @IdRes int[] value() default { View.NO_ID }; /** Listener callback to which the method will be bound. */ Callback callback() default Callback.TEXT_CHANGED; /** {@link TextWatcher} callback methods. */ enum Callback { /** {@link TextWatcher#onTextChanged(CharSequence, int, int, int)} */ @ListenerMethod( name = "onTextChanged", parameters = { "java.lang.CharSequence", "int", "int", "int" } ) TEXT_CHANGED, /** {@link TextWatcher#beforeTextChanged(CharSequence, int, int, int)} */ @ListenerMethod( name = "beforeTextChanged", parameters = { "java.lang.CharSequence", "int", "int", "int" } ) BEFORE_TEXT_CHANGED, /** {@link TextWatcher#afterTextChanged(android.text.Editable)} */ @ListenerMethod( name = "afterTextChanged", parameters = "android.text.Editable" ) AFTER_TEXT_CHANGED, } } ================================================ FILE: butterknife-annotations/src/main/java/butterknife/OnTouch.java ================================================ package butterknife; import android.view.View; import androidx.annotation.IdRes; import butterknife.internal.ListenerClass; import butterknife.internal.ListenerMethod; import java.lang.annotation.Retention; import java.lang.annotation.Target; import static android.view.View.OnTouchListener; import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** * Bind a method to an {@link OnTouchListener OnTouchListener} on the view for each ID specified. *

 * {@literal @}OnTouch(R.id.example) boolean onTouch() {
 *   Toast.makeText(this, "Touched!", Toast.LENGTH_SHORT).show();
 *   return false;
 * }
 * 
* Any number of parameters from * {@link OnTouchListener#onTouch(android.view.View, android.view.MotionEvent) onTouch} may be used * on the method. *

* If the return type of the method is {@code void}, true will be returned from the listener. * * @see OnTouchListener */ @Target(METHOD) @Retention(RUNTIME) @ListenerClass( targetType = "android.view.View", setter = "setOnTouchListener", type = "android.view.View.OnTouchListener", method = @ListenerMethod( name = "onTouch", parameters = { "android.view.View", "android.view.MotionEvent" }, returnType = "boolean", defaultReturn = "true" ) ) public @interface OnTouch { /** View IDs to which the method will be bound. */ @IdRes int[] value() default { View.NO_ID }; } ================================================ FILE: butterknife-annotations/src/main/java/butterknife/Optional.java ================================================ package butterknife; import java.lang.annotation.Retention; import java.lang.annotation.Target; import static java.lang.annotation.ElementType.METHOD; import static java.lang.annotation.RetentionPolicy.RUNTIME; /** * Denote that the view specified by the injection is not required to be present. *


 * {@literal @}Optional @OnClick(R.id.subtitle) void onSubtitleClick() {}
 * 
*/ @Target(METHOD) @Retention(RUNTIME) public @interface Optional { } ================================================ FILE: butterknife-annotations/src/main/java/butterknife/internal/Constants.java ================================================ package butterknife.internal; public class Constants { private Constants() { } public static final int NO_RES_ID = -1; } ================================================ FILE: butterknife-annotations/src/main/java/butterknife/internal/ListenerClass.java ================================================ package butterknife.internal; import java.lang.annotation.Retention; import java.lang.annotation.Target; import static java.lang.annotation.ElementType.ANNOTATION_TYPE; import static java.lang.annotation.RetentionPolicy.RUNTIME; @Retention(RUNTIME) @Target(ANNOTATION_TYPE) public @interface ListenerClass { String targetType(); /** Name of the setter method on the {@linkplain #targetType() target type} for the listener. */ String setter(); /** * Name of the method on the {@linkplain #targetType() target type} to remove the listener. If * empty {@link #setter()} will be used by default. */ String remover() default ""; /** Fully-qualified class name of the listener type. */ String type(); /** Enum which declares the listener callback methods. Mutually exclusive to {@link #method()}. */ Class> callbacks() default NONE.class; /** * Method data for single-method listener callbacks. Mutually exclusive with {@link #callbacks()} * and an error to specify more than one value. */ ListenerMethod[] method() default { }; /** Default value for {@link #callbacks()}. */ enum NONE { } } ================================================ FILE: butterknife-annotations/src/main/java/butterknife/internal/ListenerMethod.java ================================================ package butterknife.internal; import java.lang.annotation.Retention; import java.lang.annotation.Target; import static java.lang.annotation.ElementType.FIELD; import static java.lang.annotation.RetentionPolicy.RUNTIME; @Retention(RUNTIME) @Target(FIELD) public @interface ListenerMethod { /** Name of the listener method for which this annotation applies. */ String name(); /** List of method parameters. If the type is not a primitive it must be fully-qualified. */ String[] parameters() default { }; /** Primitive or fully-qualified return type of the listener method. May also be {@code void}. */ String returnType() default "void"; /** If {@link #returnType()} is not {@code void} this value is returned when no binding exists. */ String defaultReturn() default "null"; } ================================================ FILE: butterknife-compiler/build.gradle ================================================ apply plugin: 'java-library' apply plugin: 'checkstyle' sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 dependencies { implementation project(':butterknife-annotations') implementation deps.auto.common implementation deps.guava api deps.javapoet compileOnly deps.auto.service compileOnly files(org.gradle.internal.jvm.Jvm.current().getToolsJar()) api deps.incap.runtime compileOnly deps.incap.processor testImplementation deps.junit testImplementation deps.truth } checkstyle { configFile rootProject.file('checkstyle.xml') showViolations true //Remove this when tests are less verbose, i.e. using JavaPoet sourceSets = [sourceSets.main] } apply from: rootProject.file('gradle/gradle-mvn-push.gradle') ================================================ FILE: butterknife-compiler/gradle.properties ================================================ POM_NAME=Butterknife Compiler POM_ARTIFACT_ID=butterknife-compiler POM_PACKAGING=jar ================================================ FILE: butterknife-compiler/src/main/java/butterknife/compiler/BindingSet.java ================================================ package butterknife.compiler; import butterknife.OnTouch; import butterknife.internal.ListenerClass; import butterknife.internal.ListenerMethod; import com.google.common.collect.ImmutableList; import com.squareup.javapoet.AnnotationSpec; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; import com.squareup.javapoet.JavaFile; import com.squareup.javapoet.MethodSpec; import com.squareup.javapoet.ParameterizedTypeName; import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; import com.squareup.javapoet.WildcardTypeName; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import javax.annotation.Nullable; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeMirror; import static butterknife.compiler.ButterKnifeProcessor.ACTIVITY_TYPE; import static butterknife.compiler.ButterKnifeProcessor.DIALOG_TYPE; import static butterknife.compiler.ButterKnifeProcessor.VIEW_TYPE; import static butterknife.compiler.ButterKnifeProcessor.isSubtypeOfType; import static com.google.auto.common.MoreElements.getPackage; import static java.util.Collections.singletonList; import static java.util.Objects.requireNonNull; import static javax.lang.model.element.Modifier.FINAL; import static javax.lang.model.element.Modifier.PRIVATE; import static javax.lang.model.element.Modifier.PUBLIC; /** A set of all the bindings requested by a single type. */ final class BindingSet implements BindingInformationProvider { static final ClassName UTILS = ClassName.get("butterknife.internal", "Utils"); private static final ClassName VIEW = ClassName.get("android.view", "View"); private static final ClassName CONTEXT = ClassName.get("android.content", "Context"); private static final ClassName RESOURCES = ClassName.get("android.content.res", "Resources"); private static final ClassName UI_THREAD = ClassName.get("androidx.annotation", "UiThread"); private static final ClassName CALL_SUPER = ClassName.get("androidx.annotation", "CallSuper"); private static final ClassName SUPPRESS_LINT = ClassName.get("android.annotation", "SuppressLint"); private static final ClassName UNBINDER = ClassName.get("butterknife", "Unbinder"); static final ClassName BITMAP_FACTORY = ClassName.get("android.graphics", "BitmapFactory"); static final ClassName CONTEXT_COMPAT = ClassName.get("androidx.core.content", "ContextCompat"); static final ClassName ANIMATION_UTILS = ClassName.get("android.view.animation", "AnimationUtils"); private final TypeName targetTypeName; private final ClassName bindingClassName; private final TypeElement enclosingElement; private final boolean isFinal; private final boolean isView; private final boolean isActivity; private final boolean isDialog; private final ImmutableList viewBindings; private final ImmutableList collectionBindings; private final ImmutableList resourceBindings; private final @Nullable BindingInformationProvider parentBinding; private BindingSet( TypeName targetTypeName, ClassName bindingClassName, TypeElement enclosingElement, boolean isFinal, boolean isView, boolean isActivity, boolean isDialog, ImmutableList viewBindings, ImmutableList collectionBindings, ImmutableList resourceBindings, @Nullable BindingInformationProvider parentBinding) { this.isFinal = isFinal; this.targetTypeName = targetTypeName; this.bindingClassName = bindingClassName; this.enclosingElement = enclosingElement; this.isView = isView; this.isActivity = isActivity; this.isDialog = isDialog; this.viewBindings = viewBindings; this.collectionBindings = collectionBindings; this.resourceBindings = resourceBindings; this.parentBinding = parentBinding; } @Override public ClassName getBindingClassName() { return bindingClassName; } JavaFile brewJava(int sdk, boolean debuggable) { TypeSpec bindingConfiguration = createType(sdk, debuggable); return JavaFile.builder(bindingClassName.packageName(), bindingConfiguration) .addFileComment("Generated code from Butter Knife. Do not modify!") .build(); } private TypeSpec createType(int sdk, boolean debuggable) { TypeSpec.Builder result = TypeSpec.classBuilder(bindingClassName.simpleName()) .addModifiers(PUBLIC) .addOriginatingElement(enclosingElement); if (isFinal) { result.addModifiers(FINAL); } if (parentBinding != null) { result.superclass(parentBinding.getBindingClassName()); } else { result.addSuperinterface(UNBINDER); } if (hasTargetField()) { result.addField(targetTypeName, "target", PRIVATE); } if (isView) { result.addMethod(createBindingConstructorForView()); } else if (isActivity) { result.addMethod(createBindingConstructorForActivity()); } else if (isDialog) { result.addMethod(createBindingConstructorForDialog()); } if (!constructorNeedsView()) { // Add a delegating constructor with a target type + view signature for reflective use. result.addMethod(createBindingViewDelegateConstructor()); } result.addMethod(createBindingConstructor(sdk, debuggable)); if (hasViewBindings() || parentBinding == null) { result.addMethod(createBindingUnbindMethod(result)); } return result.build(); } private MethodSpec createBindingViewDelegateConstructor() { return MethodSpec.constructorBuilder() .addJavadoc("@deprecated Use {@link #$T($T, $T)} for direct creation.\n " + "Only present for runtime invocation through {@code ButterKnife.bind()}.\n", bindingClassName, targetTypeName, CONTEXT) .addAnnotation(Deprecated.class) .addAnnotation(UI_THREAD) .addModifiers(PUBLIC) .addParameter(targetTypeName, "target") .addParameter(VIEW, "source") .addStatement(("this(target, source.getContext())")) .build(); } private MethodSpec createBindingConstructorForView() { MethodSpec.Builder builder = MethodSpec.constructorBuilder() .addAnnotation(UI_THREAD) .addModifiers(PUBLIC) .addParameter(targetTypeName, "target"); if (constructorNeedsView()) { builder.addStatement("this(target, target)"); } else { builder.addStatement("this(target, target.getContext())"); } return builder.build(); } private MethodSpec createBindingConstructorForActivity() { MethodSpec.Builder builder = MethodSpec.constructorBuilder() .addAnnotation(UI_THREAD) .addModifiers(PUBLIC) .addParameter(targetTypeName, "target"); if (constructorNeedsView()) { builder.addStatement("this(target, target.getWindow().getDecorView())"); } else { builder.addStatement("this(target, target)"); } return builder.build(); } private MethodSpec createBindingConstructorForDialog() { MethodSpec.Builder builder = MethodSpec.constructorBuilder() .addAnnotation(UI_THREAD) .addModifiers(PUBLIC) .addParameter(targetTypeName, "target"); if (constructorNeedsView()) { builder.addStatement("this(target, target.getWindow().getDecorView())"); } else { builder.addStatement("this(target, target.getContext())"); } return builder.build(); } private MethodSpec createBindingConstructor(int sdk, boolean debuggable) { MethodSpec.Builder constructor = MethodSpec.constructorBuilder() .addAnnotation(UI_THREAD) .addModifiers(PUBLIC); if (hasMethodBindings()) { constructor.addParameter(targetTypeName, "target", FINAL); } else { constructor.addParameter(targetTypeName, "target"); } if (constructorNeedsView()) { constructor.addParameter(VIEW, "source"); } else { constructor.addParameter(CONTEXT, "context"); } if (hasUnqualifiedResourceBindings()) { // Aapt can change IDs out from underneath us, just suppress since all will work at runtime. constructor.addAnnotation(AnnotationSpec.builder(SuppressWarnings.class) .addMember("value", "$S", "ResourceType") .build()); } if (hasOnTouchMethodBindings()) { constructor.addAnnotation(AnnotationSpec.builder(SUPPRESS_LINT) .addMember("value", "$S", "ClickableViewAccessibility") .build()); } if (parentBinding != null) { if (parentBinding.constructorNeedsView()) { constructor.addStatement("super(target, source)"); } else if (constructorNeedsView()) { constructor.addStatement("super(target, source.getContext())"); } else { constructor.addStatement("super(target, context)"); } constructor.addCode("\n"); } if (hasTargetField()) { constructor.addStatement("this.target = target"); constructor.addCode("\n"); } if (hasViewBindings()) { if (hasViewLocal()) { // Local variable in which all views will be temporarily stored. constructor.addStatement("$T view", VIEW); } for (ViewBinding binding : viewBindings) { addViewBinding(constructor, binding, debuggable); } for (FieldCollectionViewBinding binding : collectionBindings) { constructor.addStatement("$L", binding.render(debuggable)); } if (!resourceBindings.isEmpty()) { constructor.addCode("\n"); } } if (!resourceBindings.isEmpty()) { if (constructorNeedsView()) { constructor.addStatement("$T context = source.getContext()", CONTEXT); } if (hasResourceBindingsNeedingResource(sdk)) { constructor.addStatement("$T res = context.getResources()", RESOURCES); } for (ResourceBinding binding : resourceBindings) { constructor.addStatement("$L", binding.render(sdk)); } } return constructor.build(); } private MethodSpec createBindingUnbindMethod(TypeSpec.Builder bindingClass) { MethodSpec.Builder result = MethodSpec.methodBuilder("unbind") .addAnnotation(Override.class) .addModifiers(PUBLIC); if (!isFinal && parentBinding == null) { result.addAnnotation(CALL_SUPER); } if (hasTargetField()) { if (hasFieldBindings()) { result.addStatement("$T target = this.target", targetTypeName); } result.addStatement("if (target == null) throw new $T($S)", IllegalStateException.class, "Bindings already cleared."); result.addStatement("$N = null", hasFieldBindings() ? "this.target" : "target"); result.addCode("\n"); for (ViewBinding binding : viewBindings) { if (binding.getFieldBinding() != null) { result.addStatement("target.$L = null", binding.getFieldBinding().getName()); } } for (FieldCollectionViewBinding binding : collectionBindings) { result.addStatement("target.$L = null", binding.name); } } if (hasMethodBindings()) { result.addCode("\n"); for (ViewBinding binding : viewBindings) { addFieldAndUnbindStatement(bindingClass, result, binding); } } if (parentBinding != null) { result.addCode("\n"); result.addStatement("super.unbind()"); } return result.build(); } private void addFieldAndUnbindStatement(TypeSpec.Builder result, MethodSpec.Builder unbindMethod, ViewBinding bindings) { // Only add fields to the binding if there are method bindings. Map>> classMethodBindings = bindings.getMethodBindings(); if (classMethodBindings.isEmpty()) { return; } String fieldName = bindings.isBoundToRoot() ? "viewSource" : "view" + Integer.toHexString(bindings.getId().value); result.addField(VIEW, fieldName, PRIVATE); // We only need to emit the null check if there are zero required bindings. boolean needsNullChecked = bindings.getRequiredBindings().isEmpty(); if (needsNullChecked) { unbindMethod.beginControlFlow("if ($N != null)", fieldName); } for (ListenerClass listenerClass : classMethodBindings.keySet()) { // We need to keep a reference to the listener // in case we need to unbind it via a remove method. boolean requiresRemoval = !"".equals(listenerClass.remover()); String listenerField = "null"; if (requiresRemoval) { TypeName listenerClassName = bestGuess(listenerClass.type()); listenerField = fieldName + ((ClassName) listenerClassName).simpleName(); result.addField(listenerClassName, listenerField, PRIVATE); } String targetType = listenerClass.targetType(); if (!VIEW_TYPE.equals(targetType)) { unbindMethod.addStatement("(($T) $N).$N($N)", bestGuess(targetType), fieldName, removerOrSetter(listenerClass, requiresRemoval), listenerField); } else { unbindMethod.addStatement("$N.$N($N)", fieldName, removerOrSetter(listenerClass, requiresRemoval), listenerField); } if (requiresRemoval) { unbindMethod.addStatement("$N = null", listenerField); } } unbindMethod.addStatement("$N = null", fieldName); if (needsNullChecked) { unbindMethod.endControlFlow(); } } private String removerOrSetter(ListenerClass listenerClass, boolean requiresRemoval) { return requiresRemoval ? listenerClass.remover() : listenerClass.setter(); } private void addViewBinding(MethodSpec.Builder result, ViewBinding binding, boolean debuggable) { if (binding.isSingleFieldBinding()) { // Optimize the common case where there's a single binding directly to a field. FieldViewBinding fieldBinding = requireNonNull(binding.getFieldBinding()); CodeBlock.Builder builder = CodeBlock.builder() .add("target.$L = ", fieldBinding.getName()); boolean requiresCast = requiresCast(fieldBinding.getType()); if (!debuggable || (!requiresCast && !fieldBinding.isRequired())) { if (requiresCast) { builder.add("($T) ", fieldBinding.getType()); } builder.add("source.findViewById($L)", binding.getId().code); } else { builder.add("$T.find", UTILS); builder.add(fieldBinding.isRequired() ? "RequiredView" : "OptionalView"); if (requiresCast) { builder.add("AsType"); } builder.add("(source, $L", binding.getId().code); if (fieldBinding.isRequired() || requiresCast) { builder.add(", $S", asHumanDescription(singletonList(fieldBinding))); } if (requiresCast) { builder.add(", $T.class", fieldBinding.getRawType()); } builder.add(")"); } result.addStatement("$L", builder.build()); return; } List requiredBindings = binding.getRequiredBindings(); if (!debuggable || requiredBindings.isEmpty()) { result.addStatement("view = source.findViewById($L)", binding.getId().code); } else if (!binding.isBoundToRoot()) { result.addStatement("view = $T.findRequiredView(source, $L, $S)", UTILS, binding.getId().code, asHumanDescription(requiredBindings)); } addFieldBinding(result, binding, debuggable); addMethodBindings(result, binding, debuggable); } private void addFieldBinding(MethodSpec.Builder result, ViewBinding binding, boolean debuggable) { FieldViewBinding fieldBinding = binding.getFieldBinding(); if (fieldBinding != null) { if (requiresCast(fieldBinding.getType())) { if (debuggable) { result.addStatement("target.$L = $T.castView(view, $L, $S, $T.class)", fieldBinding.getName(), UTILS, binding.getId().code, asHumanDescription(singletonList(fieldBinding)), fieldBinding.getRawType()); } else { result.addStatement("target.$L = ($T) view", fieldBinding.getName(), fieldBinding.getType()); } } else { result.addStatement("target.$L = view", fieldBinding.getName()); } } } private void addMethodBindings(MethodSpec.Builder result, ViewBinding binding, boolean debuggable) { Map>> classMethodBindings = binding.getMethodBindings(); if (classMethodBindings.isEmpty()) { return; } // We only need to emit the null check if there are zero required bindings. boolean needsNullChecked = binding.getRequiredBindings().isEmpty(); if (needsNullChecked) { result.beginControlFlow("if (view != null)"); } // Add the view reference to the binding. String fieldName = "viewSource"; String bindName = "source"; if (!binding.isBoundToRoot()) { fieldName = "view" + Integer.toHexString(binding.getId().value); bindName = "view"; } result.addStatement("$L = $N", fieldName, bindName); for (Map.Entry>> e : classMethodBindings.entrySet()) { ListenerClass listener = e.getKey(); Map> methodBindings = e.getValue(); TypeSpec.Builder callback = TypeSpec.anonymousClassBuilder("") .superclass(ClassName.bestGuess(listener.type())); for (ListenerMethod method : getListenerMethods(listener)) { MethodSpec.Builder callbackMethod = MethodSpec.methodBuilder(method.name()) .addAnnotation(Override.class) .addModifiers(PUBLIC) .returns(bestGuess(method.returnType())); String[] parameterTypes = method.parameters(); for (int i = 0, count = parameterTypes.length; i < count; i++) { callbackMethod.addParameter(bestGuess(parameterTypes[i]), "p" + i); } boolean hasReturnValue = false; CodeBlock.Builder builder = CodeBlock.builder(); Set methodViewBindings = methodBindings.get(method); if (methodViewBindings != null) { for (MethodViewBinding methodBinding : methodViewBindings) { if (methodBinding.hasReturnValue()) { hasReturnValue = true; builder.add("return "); // TODO what about multiple methods? } builder.add("target.$L(", methodBinding.getName()); List parameters = methodBinding.getParameters(); String[] listenerParameters = method.parameters(); for (int i = 0, count = parameters.size(); i < count; i++) { if (i > 0) { builder.add(", "); } Parameter parameter = parameters.get(i); int listenerPosition = parameter.getListenerPosition(); if (parameter.requiresCast(listenerParameters[listenerPosition])) { if (debuggable) { builder.add("$T.castParam(p$L, $S, $L, $S, $L, $T.class)", UTILS, listenerPosition, method.name(), listenerPosition, methodBinding.getName(), i, parameter.getType()); } else { builder.add("($T) p$L", parameter.getType(), listenerPosition); } } else { builder.add("p$L", listenerPosition); } } builder.add(");\n"); } } if (!"void".equals(method.returnType()) && !hasReturnValue) { builder.add("return $L;\n", method.defaultReturn()); } callbackMethod.addCode(builder.build()); callback.addMethod(callbackMethod.build()); } boolean requiresRemoval = listener.remover().length() != 0; String listenerField = null; if (requiresRemoval) { TypeName listenerClassName = bestGuess(listener.type()); listenerField = fieldName + ((ClassName) listenerClassName).simpleName(); result.addStatement("$L = $L", listenerField, callback.build()); } String targetType = listener.targetType(); if (!VIEW_TYPE.equals(targetType)) { result.addStatement("(($T) $N).$L($L)", bestGuess(targetType), bindName, listener.setter(), requiresRemoval ? listenerField : callback.build()); } else { result.addStatement("$N.$L($L)", bindName, listener.setter(), requiresRemoval ? listenerField : callback.build()); } } if (needsNullChecked) { result.endControlFlow(); } } private static List getListenerMethods(ListenerClass listener) { if (listener.method().length == 1) { return Arrays.asList(listener.method()); } try { List methods = new ArrayList<>(); Class> callbacks = listener.callbacks(); for (Enum callbackMethod : callbacks.getEnumConstants()) { Field callbackField = callbacks.getField(callbackMethod.name()); ListenerMethod method = callbackField.getAnnotation(ListenerMethod.class); if (method == null) { throw new IllegalStateException(String.format("@%s's %s.%s missing @%s annotation.", callbacks.getEnclosingClass().getSimpleName(), callbacks.getSimpleName(), callbackMethod.name(), ListenerMethod.class.getSimpleName())); } methods.add(method); } return methods; } catch (NoSuchFieldException e) { throw new AssertionError(e); } } static String asHumanDescription(Collection bindings) { Iterator iterator = bindings.iterator(); switch (bindings.size()) { case 1: return iterator.next().getDescription(); case 2: return iterator.next().getDescription() + " and " + iterator.next().getDescription(); default: StringBuilder builder = new StringBuilder(); for (int i = 0, count = bindings.size(); i < count; i++) { if (i != 0) { builder.append(", "); } if (i == count - 1) { builder.append("and "); } builder.append(iterator.next().getDescription()); } return builder.toString(); } } private static TypeName bestGuess(String type) { switch (type) { case "void": return TypeName.VOID; case "boolean": return TypeName.BOOLEAN; case "byte": return TypeName.BYTE; case "char": return TypeName.CHAR; case "double": return TypeName.DOUBLE; case "float": return TypeName.FLOAT; case "int": return TypeName.INT; case "long": return TypeName.LONG; case "short": return TypeName.SHORT; default: int left = type.indexOf('<'); if (left != -1) { ClassName typeClassName = ClassName.bestGuess(type.substring(0, left)); List typeArguments = new ArrayList<>(); do { typeArguments.add(WildcardTypeName.subtypeOf(Object.class)); left = type.indexOf('<', left + 1); } while (left != -1); return ParameterizedTypeName.get(typeClassName, typeArguments.toArray(new TypeName[typeArguments.size()])); } return ClassName.bestGuess(type); } } /** True when this type's bindings require a view hierarchy. */ private boolean hasViewBindings() { return !viewBindings.isEmpty() || !collectionBindings.isEmpty(); } /** True when this type's bindings use raw integer values instead of {@code R} references. */ private boolean hasUnqualifiedResourceBindings() { for (ResourceBinding binding : resourceBindings) { if (!binding.id().qualifed) { return true; } } return false; } /** True when this type's bindings use Resource directly instead of Context. */ private boolean hasResourceBindingsNeedingResource(int sdk) { for (ResourceBinding binding : resourceBindings) { if (binding.requiresResources(sdk)) { return true; } } return false; } private boolean hasMethodBindings() { for (ViewBinding bindings : viewBindings) { if (!bindings.getMethodBindings().isEmpty()) { return true; } } return false; } private boolean hasOnTouchMethodBindings() { for (ViewBinding bindings : viewBindings) { if (bindings.getMethodBindings() .containsKey(OnTouch.class.getAnnotation(ListenerClass.class))) { return true; } } return false; } private boolean hasFieldBindings() { for (ViewBinding bindings : viewBindings) { if (bindings.getFieldBinding() != null) { return true; } } return !collectionBindings.isEmpty(); } private boolean hasTargetField() { return hasFieldBindings() || hasMethodBindings(); } private boolean hasViewLocal() { for (ViewBinding bindings : viewBindings) { if (bindings.requiresLocal()) { return true; } } return false; } /** True if this binding requires a view. Otherwise only a context is needed. */ @Override public boolean constructorNeedsView() { return hasViewBindings() // || (parentBinding != null && parentBinding.constructorNeedsView()); } static boolean requiresCast(TypeName type) { return !VIEW_TYPE.equals(type.toString()); } @Override public String toString() { return bindingClassName.toString(); } static Builder newBuilder(TypeElement enclosingElement) { TypeMirror typeMirror = enclosingElement.asType(); boolean isView = isSubtypeOfType(typeMirror, VIEW_TYPE); boolean isActivity = isSubtypeOfType(typeMirror, ACTIVITY_TYPE); boolean isDialog = isSubtypeOfType(typeMirror, DIALOG_TYPE); TypeName targetType = TypeName.get(typeMirror); if (targetType instanceof ParameterizedTypeName) { targetType = ((ParameterizedTypeName) targetType).rawType; } ClassName bindingClassName = getBindingClassName(enclosingElement); boolean isFinal = enclosingElement.getModifiers().contains(Modifier.FINAL); return new Builder(targetType, bindingClassName, enclosingElement, isFinal, isView, isActivity, isDialog); } static ClassName getBindingClassName(TypeElement typeElement) { String packageName = getPackage(typeElement).getQualifiedName().toString(); String className = typeElement.getQualifiedName().toString().substring( packageName.length() + 1).replace('.', '$'); return ClassName.get(packageName, className + "_ViewBinding"); } static final class Builder { private final TypeName targetTypeName; private final ClassName bindingClassName; private final TypeElement enclosingElement; private final boolean isFinal; private final boolean isView; private final boolean isActivity; private final boolean isDialog; private @Nullable BindingInformationProvider parentBinding; private final Map viewIdMap = new LinkedHashMap<>(); private final ImmutableList.Builder collectionBindings = ImmutableList.builder(); private final ImmutableList.Builder resourceBindings = ImmutableList.builder(); private Builder( TypeName targetTypeName, ClassName bindingClassName, TypeElement enclosingElement, boolean isFinal, boolean isView, boolean isActivity, boolean isDialog) { this.targetTypeName = targetTypeName; this.bindingClassName = bindingClassName; this.enclosingElement = enclosingElement; this.isFinal = isFinal; this.isView = isView; this.isActivity = isActivity; this.isDialog = isDialog; } void addField(Id id, FieldViewBinding binding) { getOrCreateViewBindings(id).setFieldBinding(binding); } void addFieldCollection(FieldCollectionViewBinding binding) { collectionBindings.add(binding); } boolean addMethod( Id id, ListenerClass listener, ListenerMethod method, MethodViewBinding binding) { ViewBinding.Builder viewBinding = getOrCreateViewBindings(id); if (viewBinding.hasMethodBinding(listener, method) && !"void".equals(method.returnType())) { return false; } viewBinding.addMethodBinding(listener, method, binding); return true; } void addResource(ResourceBinding binding) { resourceBindings.add(binding); } void setParent(BindingInformationProvider parent) { this.parentBinding = parent; } @Nullable String findExistingBindingName(Id id) { ViewBinding.Builder builder = viewIdMap.get(id); if (builder == null) { return null; } FieldViewBinding fieldBinding = builder.fieldBinding; if (fieldBinding == null) { return null; } return fieldBinding.getName(); } private ViewBinding.Builder getOrCreateViewBindings(Id id) { ViewBinding.Builder viewId = viewIdMap.get(id); if (viewId == null) { viewId = new ViewBinding.Builder(id); viewIdMap.put(id, viewId); } return viewId; } BindingSet build() { ImmutableList.Builder viewBindings = ImmutableList.builder(); for (ViewBinding.Builder builder : viewIdMap.values()) { viewBindings.add(builder.build()); } return new BindingSet(targetTypeName, bindingClassName, enclosingElement, isFinal, isView, isActivity, isDialog, viewBindings.build(), collectionBindings.build(), resourceBindings.build(), parentBinding); } } } interface BindingInformationProvider { boolean constructorNeedsView(); ClassName getBindingClassName(); } final class ClasspathBindingSet implements BindingInformationProvider { private boolean constructorNeedsView; private ClassName className; ClasspathBindingSet(boolean constructorNeedsView, TypeElement classElement) { this.constructorNeedsView = constructorNeedsView; this.className = BindingSet.getBindingClassName(classElement); } @Override public ClassName getBindingClassName() { return className; } @Override public boolean constructorNeedsView() { return constructorNeedsView; } } ================================================ FILE: butterknife-compiler/src/main/java/butterknife/compiler/ButterKnifeProcessor.java ================================================ package butterknife.compiler; import butterknife.BindAnim; import butterknife.BindArray; import butterknife.BindBitmap; import butterknife.BindBool; import butterknife.BindColor; import butterknife.BindDimen; import butterknife.BindDrawable; import butterknife.BindFloat; import butterknife.BindFont; import butterknife.BindInt; import butterknife.BindString; import butterknife.BindView; import butterknife.BindViews; import butterknife.OnCheckedChanged; import butterknife.OnClick; import butterknife.OnEditorAction; import butterknife.OnFocusChange; import butterknife.OnItemClick; import butterknife.OnItemLongClick; import butterknife.OnItemSelected; import butterknife.OnLongClick; import butterknife.OnPageChange; import butterknife.OnTextChanged; import butterknife.OnTouch; import butterknife.Optional; import butterknife.compiler.FieldTypefaceBinding.TypefaceStyles; import butterknife.internal.ListenerClass; import butterknife.internal.ListenerMethod; import com.google.auto.common.SuperficialValidation; import com.google.auto.service.AutoService; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.squareup.javapoet.JavaFile; import com.squareup.javapoet.TypeName; import com.sun.source.util.Trees; import com.sun.tools.javac.code.Symbol; import com.sun.tools.javac.code.Type; import com.sun.tools.javac.tree.JCTree; import com.sun.tools.javac.tree.TreeScanner; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.BitSet; import java.util.Deque; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.annotation.Nullable; import javax.annotation.processing.AbstractProcessor; import javax.annotation.processing.Filer; import javax.annotation.processing.ProcessingEnvironment; import javax.annotation.processing.Processor; import javax.annotation.processing.RoundEnvironment; import javax.lang.model.SourceVersion; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; import javax.lang.model.element.Name; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.ArrayType; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeVariable; import javax.lang.model.util.Types; import javax.tools.Diagnostic.Kind; import net.ltgt.gradle.incap.IncrementalAnnotationProcessor; import net.ltgt.gradle.incap.IncrementalAnnotationProcessorType; import static butterknife.internal.Constants.NO_RES_ID; import static java.util.Objects.requireNonNull; import static javax.lang.model.element.ElementKind.CLASS; import static javax.lang.model.element.ElementKind.INTERFACE; import static javax.lang.model.element.ElementKind.METHOD; import static javax.lang.model.element.Modifier.PRIVATE; import static javax.lang.model.element.Modifier.STATIC; @AutoService(Processor.class) @IncrementalAnnotationProcessor(IncrementalAnnotationProcessorType.DYNAMIC) @SuppressWarnings("NullAway") // TODO fix all these... public final class ButterKnifeProcessor extends AbstractProcessor { // TODO remove when http://b.android.com/187527 is released. private static final String OPTION_SDK_INT = "butterknife.minSdk"; private static final String OPTION_DEBUGGABLE = "butterknife.debuggable"; static final Id NO_ID = new Id(NO_RES_ID); static final String VIEW_TYPE = "android.view.View"; static final String ACTIVITY_TYPE = "android.app.Activity"; static final String DIALOG_TYPE = "android.app.Dialog"; private static final String COLOR_STATE_LIST_TYPE = "android.content.res.ColorStateList"; private static final String BITMAP_TYPE = "android.graphics.Bitmap"; private static final String ANIMATION_TYPE = "android.view.animation.Animation"; private static final String DRAWABLE_TYPE = "android.graphics.drawable.Drawable"; private static final String TYPED_ARRAY_TYPE = "android.content.res.TypedArray"; private static final String TYPEFACE_TYPE = "android.graphics.Typeface"; private static final String NULLABLE_ANNOTATION_NAME = "Nullable"; private static final String STRING_TYPE = "java.lang.String"; private static final String LIST_TYPE = List.class.getCanonicalName(); private static final List> LISTENERS = Arrays.asList(// OnCheckedChanged.class, // OnClick.class, // OnEditorAction.class, // OnFocusChange.class, // OnItemClick.class, // OnItemLongClick.class, // OnItemSelected.class, // OnLongClick.class, // OnPageChange.class, // OnTextChanged.class, // OnTouch.class // ); private Types typeUtils; private Filer filer; private @Nullable Trees trees; private int sdk = 1; private boolean debuggable = true; private final RScanner rScanner = new RScanner(); @Override public synchronized void init(ProcessingEnvironment env) { super.init(env); String sdk = env.getOptions().get(OPTION_SDK_INT); if (sdk != null) { try { this.sdk = Integer.parseInt(sdk); } catch (NumberFormatException e) { env.getMessager() .printMessage(Kind.WARNING, "Unable to parse supplied minSdk option '" + sdk + "'. Falling back to API 1 support."); } } debuggable = !"false".equals(env.getOptions().get(OPTION_DEBUGGABLE)); typeUtils = env.getTypeUtils(); filer = env.getFiler(); try { trees = Trees.instance(processingEnv); } catch (IllegalArgumentException ignored) { try { // Get original ProcessingEnvironment from Gradle-wrapped one or KAPT-wrapped one. for (Field field : processingEnv.getClass().getDeclaredFields()) { if (field.getName().equals("delegate") || field.getName().equals("processingEnv")) { field.setAccessible(true); ProcessingEnvironment javacEnv = (ProcessingEnvironment) field.get(processingEnv); trees = Trees.instance(javacEnv); break; } } } catch (Throwable ignored2) { } } } @Override public Set getSupportedOptions() { ImmutableSet.Builder builder = ImmutableSet.builder(); builder.add(OPTION_SDK_INT, OPTION_DEBUGGABLE); if (trees != null) { builder.add(IncrementalAnnotationProcessorType.ISOLATING.getProcessorOption()); } return builder.build(); } @Override public Set getSupportedAnnotationTypes() { Set types = new LinkedHashSet<>(); for (Class annotation : getSupportedAnnotations()) { types.add(annotation.getCanonicalName()); } return types; } private Set> getSupportedAnnotations() { Set> annotations = new LinkedHashSet<>(); annotations.add(BindAnim.class); annotations.add(BindArray.class); annotations.add(BindBitmap.class); annotations.add(BindBool.class); annotations.add(BindColor.class); annotations.add(BindDimen.class); annotations.add(BindDrawable.class); annotations.add(BindFloat.class); annotations.add(BindFont.class); annotations.add(BindInt.class); annotations.add(BindString.class); annotations.add(BindView.class); annotations.add(BindViews.class); annotations.addAll(LISTENERS); return annotations; } @Override public boolean process(Set elements, RoundEnvironment env) { Map bindingMap = findAndParseTargets(env); for (Map.Entry entry : bindingMap.entrySet()) { TypeElement typeElement = entry.getKey(); BindingSet binding = entry.getValue(); JavaFile javaFile = binding.brewJava(sdk, debuggable); try { javaFile.writeTo(filer); } catch (IOException e) { error(typeElement, "Unable to write binding for type %s: %s", typeElement, e.getMessage()); } } return false; } private Map findAndParseTargets(RoundEnvironment env) { Map builderMap = new LinkedHashMap<>(); Set erasedTargetNames = new LinkedHashSet<>(); // Process each @BindAnim element. for (Element element : env.getElementsAnnotatedWith(BindAnim.class)) { if (!SuperficialValidation.validateElement(element)) continue; try { parseResourceAnimation(element, builderMap, erasedTargetNames); } catch (Exception e) { logParsingError(element, BindAnim.class, e); } } // Process each @BindArray element. for (Element element : env.getElementsAnnotatedWith(BindArray.class)) { if (!SuperficialValidation.validateElement(element)) continue; try { parseResourceArray(element, builderMap, erasedTargetNames); } catch (Exception e) { logParsingError(element, BindArray.class, e); } } // Process each @BindBitmap element. for (Element element : env.getElementsAnnotatedWith(BindBitmap.class)) { if (!SuperficialValidation.validateElement(element)) continue; try { parseResourceBitmap(element, builderMap, erasedTargetNames); } catch (Exception e) { logParsingError(element, BindBitmap.class, e); } } // Process each @BindBool element. for (Element element : env.getElementsAnnotatedWith(BindBool.class)) { if (!SuperficialValidation.validateElement(element)) continue; try { parseResourceBool(element, builderMap, erasedTargetNames); } catch (Exception e) { logParsingError(element, BindBool.class, e); } } // Process each @BindColor element. for (Element element : env.getElementsAnnotatedWith(BindColor.class)) { if (!SuperficialValidation.validateElement(element)) continue; try { parseResourceColor(element, builderMap, erasedTargetNames); } catch (Exception e) { logParsingError(element, BindColor.class, e); } } // Process each @BindDimen element. for (Element element : env.getElementsAnnotatedWith(BindDimen.class)) { if (!SuperficialValidation.validateElement(element)) continue; try { parseResourceDimen(element, builderMap, erasedTargetNames); } catch (Exception e) { logParsingError(element, BindDimen.class, e); } } // Process each @BindDrawable element. for (Element element : env.getElementsAnnotatedWith(BindDrawable.class)) { if (!SuperficialValidation.validateElement(element)) continue; try { parseResourceDrawable(element, builderMap, erasedTargetNames); } catch (Exception e) { logParsingError(element, BindDrawable.class, e); } } // Process each @BindFloat element. for (Element element : env.getElementsAnnotatedWith(BindFloat.class)) { if (!SuperficialValidation.validateElement(element)) continue; try { parseResourceFloat(element, builderMap, erasedTargetNames); } catch (Exception e) { logParsingError(element, BindFloat.class, e); } } // Process each @BindFont element. for (Element element : env.getElementsAnnotatedWith(BindFont.class)) { if (!SuperficialValidation.validateElement(element)) continue; try { parseResourceFont(element, builderMap, erasedTargetNames); } catch (Exception e) { logParsingError(element, BindFont.class, e); } } // Process each @BindInt element. for (Element element : env.getElementsAnnotatedWith(BindInt.class)) { if (!SuperficialValidation.validateElement(element)) continue; try { parseResourceInt(element, builderMap, erasedTargetNames); } catch (Exception e) { logParsingError(element, BindInt.class, e); } } // Process each @BindString element. for (Element element : env.getElementsAnnotatedWith(BindString.class)) { if (!SuperficialValidation.validateElement(element)) continue; try { parseResourceString(element, builderMap, erasedTargetNames); } catch (Exception e) { logParsingError(element, BindString.class, e); } } // Process each @BindView element. for (Element element : env.getElementsAnnotatedWith(BindView.class)) { // we don't SuperficialValidation.validateElement(element) // so that an unresolved View type can be generated by later processing rounds try { parseBindView(element, builderMap, erasedTargetNames); } catch (Exception e) { logParsingError(element, BindView.class, e); } } // Process each @BindViews element. for (Element element : env.getElementsAnnotatedWith(BindViews.class)) { // we don't SuperficialValidation.validateElement(element) // so that an unresolved View type can be generated by later processing rounds try { parseBindViews(element, builderMap, erasedTargetNames); } catch (Exception e) { logParsingError(element, BindViews.class, e); } } // Process each annotation that corresponds to a listener. for (Class listener : LISTENERS) { findAndParseListener(env, listener, builderMap, erasedTargetNames); } Map classpathBindings = findAllSupertypeBindings(builderMap, erasedTargetNames); // Associate superclass binders with their subclass binders. This is a queue-based tree walk // which starts at the roots (superclasses) and walks to the leafs (subclasses). Deque> entries = new ArrayDeque<>(builderMap.entrySet()); Map bindingMap = new LinkedHashMap<>(); while (!entries.isEmpty()) { Map.Entry entry = entries.removeFirst(); TypeElement type = entry.getKey(); BindingSet.Builder builder = entry.getValue(); TypeElement parentType = findParentType(type, erasedTargetNames, classpathBindings.keySet()); if (parentType == null) { bindingMap.put(type, builder.build()); } else { BindingInformationProvider parentBinding = bindingMap.get(parentType); if (parentBinding == null) { parentBinding = classpathBindings.get(parentType); } if (parentBinding != null) { builder.setParent(parentBinding); bindingMap.put(type, builder.build()); } else { // Has a superclass binding but we haven't built it yet. Re-enqueue for later. entries.addLast(entry); } } } return bindingMap; } private void logParsingError(Element element, Class annotation, Exception e) { StringWriter stackTrace = new StringWriter(); e.printStackTrace(new PrintWriter(stackTrace)); error(element, "Unable to parse @%s binding.\n\n%s", annotation.getSimpleName(), stackTrace); } private boolean isInaccessibleViaGeneratedCode(Class annotationClass, String targetThing, Element element) { boolean hasError = false; TypeElement enclosingElement = (TypeElement) element.getEnclosingElement(); // Verify field or method modifiers. Set modifiers = element.getModifiers(); if (modifiers.contains(PRIVATE) || modifiers.contains(STATIC)) { error(element, "@%s %s must not be private or static. (%s.%s)", annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(), element.getSimpleName()); hasError = true; } // Verify containing type. if (enclosingElement.getKind() != CLASS) { error(enclosingElement, "@%s %s may only be contained in classes. (%s.%s)", annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(), element.getSimpleName()); hasError = true; } // Verify containing class visibility is not private. if (enclosingElement.getModifiers().contains(PRIVATE)) { error(enclosingElement, "@%s %s may not be contained in private classes. (%s.%s)", annotationClass.getSimpleName(), targetThing, enclosingElement.getQualifiedName(), element.getSimpleName()); hasError = true; } return hasError; } private boolean isBindingInWrongPackage(Class annotationClass, Element element) { TypeElement enclosingElement = (TypeElement) element.getEnclosingElement(); String qualifiedName = enclosingElement.getQualifiedName().toString(); if (qualifiedName.startsWith("android.")) { error(element, "@%s-annotated class incorrectly in Android framework package. (%s)", annotationClass.getSimpleName(), qualifiedName); return true; } if (qualifiedName.startsWith("java.")) { error(element, "@%s-annotated class incorrectly in Java framework package. (%s)", annotationClass.getSimpleName(), qualifiedName); return true; } return false; } private void parseBindView(Element element, Map builderMap, Set erasedTargetNames) { TypeElement enclosingElement = (TypeElement) element.getEnclosingElement(); // Start by verifying common generated code restrictions. boolean hasError = isInaccessibleViaGeneratedCode(BindView.class, "fields", element) || isBindingInWrongPackage(BindView.class, element); // Verify that the target type extends from View. TypeMirror elementType = element.asType(); if (elementType.getKind() == TypeKind.TYPEVAR) { TypeVariable typeVariable = (TypeVariable) elementType; elementType = typeVariable.getUpperBound(); } Name qualifiedName = enclosingElement.getQualifiedName(); Name simpleName = element.getSimpleName(); if (!isSubtypeOfType(elementType, VIEW_TYPE) && !isInterface(elementType)) { if (elementType.getKind() == TypeKind.ERROR) { note(element, "@%s field with unresolved type (%s) " + "must elsewhere be generated as a View or interface. (%s.%s)", BindView.class.getSimpleName(), elementType, qualifiedName, simpleName); } else { error(element, "@%s fields must extend from View or be an interface. (%s.%s)", BindView.class.getSimpleName(), qualifiedName, simpleName); hasError = true; } } if (hasError) { return; } // Assemble information on the field. int id = element.getAnnotation(BindView.class).value(); BindingSet.Builder builder = builderMap.get(enclosingElement); Id resourceId = elementToId(element, BindView.class, id); if (builder != null) { String existingBindingName = builder.findExistingBindingName(resourceId); if (existingBindingName != null) { error(element, "Attempt to use @%s for an already bound ID %d on '%s'. (%s.%s)", BindView.class.getSimpleName(), id, existingBindingName, enclosingElement.getQualifiedName(), element.getSimpleName()); return; } } else { builder = getOrCreateBindingBuilder(builderMap, enclosingElement); } String name = simpleName.toString(); TypeName type = TypeName.get(elementType); boolean required = isFieldRequired(element); builder.addField(resourceId, new FieldViewBinding(name, type, required)); // Add the type-erased version to the valid binding targets set. erasedTargetNames.add(enclosingElement); } private void parseBindViews(Element element, Map builderMap, Set erasedTargetNames) { TypeElement enclosingElement = (TypeElement) element.getEnclosingElement(); // Start by verifying common generated code restrictions. boolean hasError = isInaccessibleViaGeneratedCode(BindViews.class, "fields", element) || isBindingInWrongPackage(BindViews.class, element); // Verify that the type is a List or an array. TypeMirror elementType = element.asType(); String erasedType = doubleErasure(elementType); TypeMirror viewType = null; FieldCollectionViewBinding.Kind kind = null; if (elementType.getKind() == TypeKind.ARRAY) { ArrayType arrayType = (ArrayType) elementType; viewType = arrayType.getComponentType(); kind = FieldCollectionViewBinding.Kind.ARRAY; } else if (LIST_TYPE.equals(erasedType)) { DeclaredType declaredType = (DeclaredType) elementType; List typeArguments = declaredType.getTypeArguments(); if (typeArguments.size() != 1) { error(element, "@%s List must have a generic component. (%s.%s)", BindViews.class.getSimpleName(), enclosingElement.getQualifiedName(), element.getSimpleName()); hasError = true; } else { viewType = typeArguments.get(0); } kind = FieldCollectionViewBinding.Kind.LIST; } else { error(element, "@%s must be a List or array. (%s.%s)", BindViews.class.getSimpleName(), enclosingElement.getQualifiedName(), element.getSimpleName()); hasError = true; } if (viewType != null && viewType.getKind() == TypeKind.TYPEVAR) { TypeVariable typeVariable = (TypeVariable) viewType; viewType = typeVariable.getUpperBound(); } // Verify that the target type extends from View. if (viewType != null && !isSubtypeOfType(viewType, VIEW_TYPE) && !isInterface(viewType)) { if (viewType.getKind() == TypeKind.ERROR) { note(element, "@%s List or array with unresolved type (%s) " + "must elsewhere be generated as a View or interface. (%s.%s)", BindViews.class.getSimpleName(), viewType, enclosingElement.getQualifiedName(), element.getSimpleName()); } else { error(element, "@%s List or array type must extend from View or be an interface. (%s.%s)", BindViews.class.getSimpleName(), enclosingElement.getQualifiedName(), element.getSimpleName()); hasError = true; } } // Assemble information on the field. String name = element.getSimpleName().toString(); int[] ids = element.getAnnotation(BindViews.class).value(); if (ids.length == 0) { error(element, "@%s must specify at least one ID. (%s.%s)", BindViews.class.getSimpleName(), enclosingElement.getQualifiedName(), element.getSimpleName()); hasError = true; } Integer duplicateId = findDuplicate(ids); if (duplicateId != null) { error(element, "@%s annotation contains duplicate ID %d. (%s.%s)", BindViews.class.getSimpleName(), duplicateId, enclosingElement.getQualifiedName(), element.getSimpleName()); hasError = true; } if (hasError) { return; } TypeName type = TypeName.get(requireNonNull(viewType)); boolean required = isFieldRequired(element); BindingSet.Builder builder = getOrCreateBindingBuilder(builderMap, enclosingElement); builder.addFieldCollection(new FieldCollectionViewBinding(name, type, requireNonNull(kind), new ArrayList<>(elementToIds(element, BindViews.class, ids).values()), required)); erasedTargetNames.add(enclosingElement); } private void parseResourceAnimation(Element element, Map builderMap, Set erasedTargetNames) { boolean hasError = false; TypeElement enclosingElement = (TypeElement) element.getEnclosingElement(); // Verify that the target type is Animation. if (!ANIMATION_TYPE.equals(element.asType().toString())) { error(element, "@%s field type must be 'Animation'. (%s.%s)", BindAnim.class.getSimpleName(), enclosingElement.getQualifiedName(), element.getSimpleName()); hasError = true; } // Verify common generated code restrictions. hasError |= isInaccessibleViaGeneratedCode(BindAnim.class, "fields", element); hasError |= isBindingInWrongPackage(BindAnim.class, element); if (hasError) { return; } // Assemble information on the field. String name = element.getSimpleName().toString(); int id = element.getAnnotation(BindAnim.class).value(); Id resourceId = elementToId(element, BindAnim.class, id); BindingSet.Builder builder = getOrCreateBindingBuilder(builderMap, enclosingElement); builder.addResource(new FieldAnimationBinding(resourceId, name)); erasedTargetNames.add(enclosingElement); } private void parseResourceBool(Element element, Map builderMap, Set erasedTargetNames) { boolean hasError = false; TypeElement enclosingElement = (TypeElement) element.getEnclosingElement(); // Verify that the target type is bool. if (element.asType().getKind() != TypeKind.BOOLEAN) { error(element, "@%s field type must be 'boolean'. (%s.%s)", BindBool.class.getSimpleName(), enclosingElement.getQualifiedName(), element.getSimpleName()); hasError = true; } // Verify common generated code restrictions. hasError |= isInaccessibleViaGeneratedCode(BindBool.class, "fields", element); hasError |= isBindingInWrongPackage(BindBool.class, element); if (hasError) { return; } // Assemble information on the field. String name = element.getSimpleName().toString(); int id = element.getAnnotation(BindBool.class).value(); Id resourceId = elementToId(element, BindBool.class, id); BindingSet.Builder builder = getOrCreateBindingBuilder(builderMap, enclosingElement); builder.addResource( new FieldResourceBinding(resourceId, name, FieldResourceBinding.Type.BOOL)); erasedTargetNames.add(enclosingElement); } private void parseResourceColor(Element element, Map builderMap, Set erasedTargetNames) { boolean hasError = false; TypeElement enclosingElement = (TypeElement) element.getEnclosingElement(); // Verify that the target type is int or ColorStateList. boolean isColorStateList = false; TypeMirror elementType = element.asType(); if (COLOR_STATE_LIST_TYPE.equals(elementType.toString())) { isColorStateList = true; } else if (elementType.getKind() != TypeKind.INT) { error(element, "@%s field type must be 'int' or 'ColorStateList'. (%s.%s)", BindColor.class.getSimpleName(), enclosingElement.getQualifiedName(), element.getSimpleName()); hasError = true; } // Verify common generated code restrictions. hasError |= isInaccessibleViaGeneratedCode(BindColor.class, "fields", element); hasError |= isBindingInWrongPackage(BindColor.class, element); if (hasError) { return; } // Assemble information on the field. String name = element.getSimpleName().toString(); int id = element.getAnnotation(BindColor.class).value(); Id resourceId = elementToId(element, BindColor.class, id); BindingSet.Builder builder = getOrCreateBindingBuilder(builderMap, enclosingElement); FieldResourceBinding.Type colorStateList = FieldResourceBinding.Type.COLOR_STATE_LIST; FieldResourceBinding.Type color = FieldResourceBinding.Type.COLOR; builder.addResource(new FieldResourceBinding( resourceId, name, isColorStateList ? colorStateList : color)); erasedTargetNames.add(enclosingElement); } private void parseResourceDimen(Element element, Map builderMap, Set erasedTargetNames) { boolean hasError = false; TypeElement enclosingElement = (TypeElement) element.getEnclosingElement(); // Verify that the target type is int or ColorStateList. boolean isInt = false; TypeMirror elementType = element.asType(); if (elementType.getKind() == TypeKind.INT) { isInt = true; } else if (elementType.getKind() != TypeKind.FLOAT) { error(element, "@%s field type must be 'int' or 'float'. (%s.%s)", BindDimen.class.getSimpleName(), enclosingElement.getQualifiedName(), element.getSimpleName()); hasError = true; } // Verify common generated code restrictions. hasError |= isInaccessibleViaGeneratedCode(BindDimen.class, "fields", element); hasError |= isBindingInWrongPackage(BindDimen.class, element); if (hasError) { return; } // Assemble information on the field. String name = element.getSimpleName().toString(); int id = element.getAnnotation(BindDimen.class).value(); Id resourceId = elementToId(element, BindDimen.class, id); BindingSet.Builder builder = getOrCreateBindingBuilder(builderMap, enclosingElement); builder.addResource(new FieldResourceBinding(resourceId, name, isInt ? FieldResourceBinding.Type.DIMEN_AS_INT : FieldResourceBinding.Type.DIMEN_AS_FLOAT)); erasedTargetNames.add(enclosingElement); } private void parseResourceBitmap(Element element, Map builderMap, Set erasedTargetNames) { boolean hasError = false; TypeElement enclosingElement = (TypeElement) element.getEnclosingElement(); // Verify that the target type is Bitmap. if (!BITMAP_TYPE.equals(element.asType().toString())) { error(element, "@%s field type must be 'Bitmap'. (%s.%s)", BindBitmap.class.getSimpleName(), enclosingElement.getQualifiedName(), element.getSimpleName()); hasError = true; } // Verify common generated code restrictions. hasError |= isInaccessibleViaGeneratedCode(BindBitmap.class, "fields", element); hasError |= isBindingInWrongPackage(BindBitmap.class, element); if (hasError) { return; } // Assemble information on the field. String name = element.getSimpleName().toString(); int id = element.getAnnotation(BindBitmap.class).value(); Id resourceId = elementToId(element, BindBitmap.class, id); BindingSet.Builder builder = getOrCreateBindingBuilder(builderMap, enclosingElement); builder.addResource( new FieldResourceBinding(resourceId, name, FieldResourceBinding.Type.BITMAP)); erasedTargetNames.add(enclosingElement); } private void parseResourceDrawable(Element element, Map builderMap, Set erasedTargetNames) { boolean hasError = false; TypeElement enclosingElement = (TypeElement) element.getEnclosingElement(); // Verify that the target type is Drawable. if (!DRAWABLE_TYPE.equals(element.asType().toString())) { error(element, "@%s field type must be 'Drawable'. (%s.%s)", BindDrawable.class.getSimpleName(), enclosingElement.getQualifiedName(), element.getSimpleName()); hasError = true; } // Verify common generated code restrictions. hasError |= isInaccessibleViaGeneratedCode(BindDrawable.class, "fields", element); hasError |= isBindingInWrongPackage(BindDrawable.class, element); if (hasError) { return; } // Assemble information on the field. String name = element.getSimpleName().toString(); int id = element.getAnnotation(BindDrawable.class).value(); int tint = element.getAnnotation(BindDrawable.class).tint(); Map resourceIds = elementToIds(element, BindDrawable.class, new int[] {id, tint}); BindingSet.Builder builder = getOrCreateBindingBuilder(builderMap, enclosingElement); builder.addResource(new FieldDrawableBinding(resourceIds.get(id), name, resourceIds.get(tint))); erasedTargetNames.add(enclosingElement); } private void parseResourceFloat(Element element, Map builderMap, Set erasedTargetNames) { boolean hasError = false; TypeElement enclosingElement = (TypeElement) element.getEnclosingElement(); // Verify that the target type is float. if (element.asType().getKind() != TypeKind.FLOAT) { error(element, "@%s field type must be 'float'. (%s.%s)", BindFloat.class.getSimpleName(), enclosingElement.getQualifiedName(), element.getSimpleName()); hasError = true; } // Verify common generated code restrictions. hasError |= isInaccessibleViaGeneratedCode(BindFloat.class, "fields", element); hasError |= isBindingInWrongPackage(BindFloat.class, element); if (hasError) { return; } // Assemble information on the field. String name = element.getSimpleName().toString(); int id = element.getAnnotation(BindFloat.class).value(); Id resourceId = elementToId(element, BindFloat.class, id); BindingSet.Builder builder = getOrCreateBindingBuilder(builderMap, enclosingElement); builder.addResource( new FieldResourceBinding(resourceId, name, FieldResourceBinding.Type.FLOAT)); erasedTargetNames.add(enclosingElement); } private void parseResourceFont(Element element, Map builderMap, Set erasedTargetNames) { boolean hasError = false; TypeElement enclosingElement = (TypeElement) element.getEnclosingElement(); // Verify that the target type is a Typeface. if (!TYPEFACE_TYPE.equals(element.asType().toString())) { error(element, "@%s field type must be 'Typeface'. (%s.%s)", BindFont.class.getSimpleName(), enclosingElement.getQualifiedName(), element.getSimpleName()); hasError = true; } // Verify common generated code restrictions. hasError |= isInaccessibleViaGeneratedCode(BindFont.class, "fields", element); hasError |= isBindingInWrongPackage(BindFont.class, element); // Assemble information on the field. String name = element.getSimpleName().toString(); BindFont bindFont = element.getAnnotation(BindFont.class); int styleValue = bindFont.style(); TypefaceStyles style = TypefaceStyles.fromValue(styleValue); if (style == null) { error(element, "@%s style must be NORMAL, BOLD, ITALIC, or BOLD_ITALIC. (%s.%s)", BindFont.class.getSimpleName(), enclosingElement.getQualifiedName(), name); hasError = true; } if (hasError) { return; } BindingSet.Builder builder = getOrCreateBindingBuilder(builderMap, enclosingElement); Id resourceId = elementToId(element, BindFont.class, bindFont.value()); builder.addResource(new FieldTypefaceBinding(resourceId, name, style)); erasedTargetNames.add(enclosingElement); } private void parseResourceInt(Element element, Map builderMap, Set erasedTargetNames) { boolean hasError = false; TypeElement enclosingElement = (TypeElement) element.getEnclosingElement(); // Verify that the target type is int. if (element.asType().getKind() != TypeKind.INT) { error(element, "@%s field type must be 'int'. (%s.%s)", BindInt.class.getSimpleName(), enclosingElement.getQualifiedName(), element.getSimpleName()); hasError = true; } // Verify common generated code restrictions. hasError |= isInaccessibleViaGeneratedCode(BindInt.class, "fields", element); hasError |= isBindingInWrongPackage(BindInt.class, element); if (hasError) { return; } // Assemble information on the field. String name = element.getSimpleName().toString(); int id = element.getAnnotation(BindInt.class).value(); Id resourceId = elementToId(element, BindInt.class, id); BindingSet.Builder builder = getOrCreateBindingBuilder(builderMap, enclosingElement); builder.addResource( new FieldResourceBinding(resourceId, name, FieldResourceBinding.Type.INT)); erasedTargetNames.add(enclosingElement); } private void parseResourceString(Element element, Map builderMap, Set erasedTargetNames) { boolean hasError = false; TypeElement enclosingElement = (TypeElement) element.getEnclosingElement(); // Verify that the target type is String. if (!STRING_TYPE.equals(element.asType().toString())) { error(element, "@%s field type must be 'String'. (%s.%s)", BindString.class.getSimpleName(), enclosingElement.getQualifiedName(), element.getSimpleName()); hasError = true; } // Verify common generated code restrictions. hasError |= isInaccessibleViaGeneratedCode(BindString.class, "fields", element); hasError |= isBindingInWrongPackage(BindString.class, element); if (hasError) { return; } // Assemble information on the field. String name = element.getSimpleName().toString(); int id = element.getAnnotation(BindString.class).value(); Id resourceId = elementToId(element, BindString.class, id); BindingSet.Builder builder = getOrCreateBindingBuilder(builderMap, enclosingElement); builder.addResource( new FieldResourceBinding(resourceId, name, FieldResourceBinding.Type.STRING)); erasedTargetNames.add(enclosingElement); } private void parseResourceArray(Element element, Map builderMap, Set erasedTargetNames) { boolean hasError = false; TypeElement enclosingElement = (TypeElement) element.getEnclosingElement(); // Verify that the target type is supported. FieldResourceBinding.Type type = getArrayResourceMethodName(element); if (type == null) { error(element, "@%s field type must be one of: String[], int[], CharSequence[], %s. (%s.%s)", BindArray.class.getSimpleName(), TYPED_ARRAY_TYPE, enclosingElement.getQualifiedName(), element.getSimpleName()); hasError = true; } // Verify common generated code restrictions. hasError |= isInaccessibleViaGeneratedCode(BindArray.class, "fields", element); hasError |= isBindingInWrongPackage(BindArray.class, element); if (hasError) { return; } // Assemble information on the field. String name = element.getSimpleName().toString(); int id = element.getAnnotation(BindArray.class).value(); Id resourceId = elementToId(element, BindArray.class, id); BindingSet.Builder builder = getOrCreateBindingBuilder(builderMap, enclosingElement); builder.addResource(new FieldResourceBinding(resourceId, name, requireNonNull(type))); erasedTargetNames.add(enclosingElement); } /** * Returns a method name from the {@code android.content.res.Resources} class for array resource * binding, null if the element type is not supported. */ private static @Nullable FieldResourceBinding.Type getArrayResourceMethodName(Element element) { TypeMirror typeMirror = element.asType(); if (TYPED_ARRAY_TYPE.equals(typeMirror.toString())) { return FieldResourceBinding.Type.TYPED_ARRAY; } if (TypeKind.ARRAY.equals(typeMirror.getKind())) { ArrayType arrayType = (ArrayType) typeMirror; String componentType = arrayType.getComponentType().toString(); if (STRING_TYPE.equals(componentType)) { return FieldResourceBinding.Type.STRING_ARRAY; } else if ("int".equals(componentType)) { return FieldResourceBinding.Type.INT_ARRAY; } else if ("java.lang.CharSequence".equals(componentType)) { return FieldResourceBinding.Type.TEXT_ARRAY; } } return null; } /** Returns the first duplicate element inside an array, null if there are no duplicates. */ private static @Nullable Integer findDuplicate(int[] array) { Set seenElements = new LinkedHashSet<>(); for (int element : array) { if (!seenElements.add(element)) { return element; } } return null; } /** Uses both {@link Types#erasure} and string manipulation to strip any generic types. */ private String doubleErasure(TypeMirror elementType) { String name = typeUtils.erasure(elementType).toString(); int typeParamStart = name.indexOf('<'); if (typeParamStart != -1) { name = name.substring(0, typeParamStart); } return name; } private void findAndParseListener(RoundEnvironment env, Class annotationClass, Map builderMap, Set erasedTargetNames) { for (Element element : env.getElementsAnnotatedWith(annotationClass)) { if (!SuperficialValidation.validateElement(element)) continue; try { parseListenerAnnotation(annotationClass, element, builderMap, erasedTargetNames); } catch (Exception e) { StringWriter stackTrace = new StringWriter(); e.printStackTrace(new PrintWriter(stackTrace)); error(element, "Unable to generate view binder for @%s.\n\n%s", annotationClass.getSimpleName(), stackTrace.toString()); } } } private void parseListenerAnnotation(Class annotationClass, Element element, Map builderMap, Set erasedTargetNames) throws Exception { // This should be guarded by the annotation's @Target but it's worth a check for safe casting. if (!(element instanceof ExecutableElement) || element.getKind() != METHOD) { throw new IllegalStateException( String.format("@%s annotation must be on a method.", annotationClass.getSimpleName())); } ExecutableElement executableElement = (ExecutableElement) element; TypeElement enclosingElement = (TypeElement) element.getEnclosingElement(); // Assemble information on the method. Annotation annotation = element.getAnnotation(annotationClass); Method annotationValue = annotationClass.getDeclaredMethod("value"); if (annotationValue.getReturnType() != int[].class) { throw new IllegalStateException( String.format("@%s annotation value() type not int[].", annotationClass)); } int[] ids = (int[]) annotationValue.invoke(annotation); String name = executableElement.getSimpleName().toString(); boolean required = isListenerRequired(executableElement); // Verify that the method and its containing class are accessible via generated code. boolean hasError = isInaccessibleViaGeneratedCode(annotationClass, "methods", element); hasError |= isBindingInWrongPackage(annotationClass, element); Integer duplicateId = findDuplicate(ids); if (duplicateId != null) { error(element, "@%s annotation for method contains duplicate ID %d. (%s.%s)", annotationClass.getSimpleName(), duplicateId, enclosingElement.getQualifiedName(), element.getSimpleName()); hasError = true; } ListenerClass listener = annotationClass.getAnnotation(ListenerClass.class); if (listener == null) { throw new IllegalStateException( String.format("No @%s defined on @%s.", ListenerClass.class.getSimpleName(), annotationClass.getSimpleName())); } for (int id : ids) { if (id == NO_ID.value) { if (ids.length == 1) { if (!required) { error(element, "ID-free binding must not be annotated with @Optional. (%s.%s)", enclosingElement.getQualifiedName(), element.getSimpleName()); hasError = true; } } else { error(element, "@%s annotation contains invalid ID %d. (%s.%s)", annotationClass.getSimpleName(), id, enclosingElement.getQualifiedName(), element.getSimpleName()); hasError = true; } } } ListenerMethod method; ListenerMethod[] methods = listener.method(); if (methods.length > 1) { throw new IllegalStateException(String.format("Multiple listener methods specified on @%s.", annotationClass.getSimpleName())); } else if (methods.length == 1) { if (listener.callbacks() != ListenerClass.NONE.class) { throw new IllegalStateException( String.format("Both method() and callback() defined on @%s.", annotationClass.getSimpleName())); } method = methods[0]; } else { Method annotationCallback = annotationClass.getDeclaredMethod("callback"); Enum callback = (Enum) annotationCallback.invoke(annotation); Field callbackField = callback.getDeclaringClass().getField(callback.name()); method = callbackField.getAnnotation(ListenerMethod.class); if (method == null) { throw new IllegalStateException( String.format("No @%s defined on @%s's %s.%s.", ListenerMethod.class.getSimpleName(), annotationClass.getSimpleName(), callback.getDeclaringClass().getSimpleName(), callback.name())); } } // Verify that the method has equal to or less than the number of parameters as the listener. List methodParameters = executableElement.getParameters(); if (methodParameters.size() > method.parameters().length) { error(element, "@%s methods can have at most %s parameter(s). (%s.%s)", annotationClass.getSimpleName(), method.parameters().length, enclosingElement.getQualifiedName(), element.getSimpleName()); hasError = true; } // Verify method return type matches the listener. TypeMirror returnType = executableElement.getReturnType(); if (returnType instanceof TypeVariable) { TypeVariable typeVariable = (TypeVariable) returnType; returnType = typeVariable.getUpperBound(); } String returnTypeString = returnType.toString(); boolean hasReturnValue = !"void".equals(returnTypeString); if (!returnTypeString.equals(method.returnType()) && hasReturnValue) { error(element, "@%s methods must have a '%s' return type. (%s.%s)", annotationClass.getSimpleName(), method.returnType(), enclosingElement.getQualifiedName(), element.getSimpleName()); hasError = true; } if (hasError) { return; } Parameter[] parameters = Parameter.NONE; if (!methodParameters.isEmpty()) { parameters = new Parameter[methodParameters.size()]; BitSet methodParameterUsed = new BitSet(methodParameters.size()); String[] parameterTypes = method.parameters(); for (int i = 0; i < methodParameters.size(); i++) { VariableElement methodParameter = methodParameters.get(i); TypeMirror methodParameterType = methodParameter.asType(); if (methodParameterType instanceof TypeVariable) { TypeVariable typeVariable = (TypeVariable) methodParameterType; methodParameterType = typeVariable.getUpperBound(); } for (int j = 0; j < parameterTypes.length; j++) { if (methodParameterUsed.get(j)) { continue; } if ((isSubtypeOfType(methodParameterType, parameterTypes[j]) && isSubtypeOfType(methodParameterType, VIEW_TYPE)) || isTypeEqual(methodParameterType, parameterTypes[j]) || isInterface(methodParameterType)) { parameters[i] = new Parameter(j, TypeName.get(methodParameterType)); methodParameterUsed.set(j); break; } } if (parameters[i] == null) { StringBuilder builder = new StringBuilder(); builder.append("Unable to match @") .append(annotationClass.getSimpleName()) .append(" method arguments. (") .append(enclosingElement.getQualifiedName()) .append('.') .append(element.getSimpleName()) .append(')'); for (int j = 0; j < parameters.length; j++) { Parameter parameter = parameters[j]; builder.append("\n\n Parameter #") .append(j + 1) .append(": ") .append(methodParameters.get(j).asType().toString()) .append("\n "); if (parameter == null) { builder.append("did not match any listener parameters"); } else { builder.append("matched listener parameter #") .append(parameter.getListenerPosition() + 1) .append(": ") .append(parameter.getType()); } } builder.append("\n\nMethods may have up to ") .append(method.parameters().length) .append(" parameter(s):\n"); for (String parameterType : method.parameters()) { builder.append("\n ").append(parameterType); } builder.append( "\n\nThese may be listed in any order but will be searched for from top to bottom."); error(executableElement, builder.toString()); return; } } } MethodViewBinding binding = new MethodViewBinding(name, Arrays.asList(parameters), required, hasReturnValue); BindingSet.Builder builder = getOrCreateBindingBuilder(builderMap, enclosingElement); Map resourceIds = elementToIds(element, annotationClass, ids); for (Map.Entry entry : resourceIds.entrySet()) { if (!builder.addMethod(entry.getValue(), listener, method, binding)) { error(element, "Multiple listener methods with return value specified for ID %d. (%s.%s)", entry.getKey(), enclosingElement.getQualifiedName(), element.getSimpleName()); return; } } // Add the type-erased version to the valid binding targets set. erasedTargetNames.add(enclosingElement); } private boolean isInterface(TypeMirror typeMirror) { return typeMirror instanceof DeclaredType && ((DeclaredType) typeMirror).asElement().getKind() == INTERFACE; } static boolean isSubtypeOfType(TypeMirror typeMirror, String otherType) { if (isTypeEqual(typeMirror, otherType)) { return true; } if (typeMirror.getKind() != TypeKind.DECLARED) { return false; } DeclaredType declaredType = (DeclaredType) typeMirror; List typeArguments = declaredType.getTypeArguments(); if (typeArguments.size() > 0) { StringBuilder typeString = new StringBuilder(declaredType.asElement().toString()); typeString.append('<'); for (int i = 0; i < typeArguments.size(); i++) { if (i > 0) { typeString.append(','); } typeString.append('?'); } typeString.append('>'); if (typeString.toString().equals(otherType)) { return true; } } Element element = declaredType.asElement(); if (!(element instanceof TypeElement)) { return false; } TypeElement typeElement = (TypeElement) element; TypeMirror superType = typeElement.getSuperclass(); if (isSubtypeOfType(superType, otherType)) { return true; } for (TypeMirror interfaceType : typeElement.getInterfaces()) { if (isSubtypeOfType(interfaceType, otherType)) { return true; } } return false; } private static boolean isTypeEqual(TypeMirror typeMirror, String otherType) { return otherType.equals(typeMirror.toString()); } private BindingSet.Builder getOrCreateBindingBuilder( Map builderMap, TypeElement enclosingElement) { BindingSet.Builder builder = builderMap.get(enclosingElement); if (builder == null) { builder = BindingSet.newBuilder(enclosingElement); builderMap.put(enclosingElement, builder); } return builder; } /** Finds the parent binder type in the supplied sets, if any. */ private @Nullable TypeElement findParentType( TypeElement typeElement, Set parents, Set classpathParents) { while (true) { typeElement = getSuperClass(typeElement); if (typeElement == null || parents.contains(typeElement) || classpathParents.contains(typeElement)) { return typeElement; } } } private Map findAllSupertypeBindings( Map builderMap, Set processedInThisRound) { Map classpathBindings = new HashMap<>(); Set> supportedAnnotations = getSupportedAnnotations(); Set> requireViewInConstructor = ImmutableSet.>builder() .addAll(LISTENERS).add(BindView.class).add(BindViews.class).build(); supportedAnnotations.removeAll(requireViewInConstructor); for (TypeElement typeElement : builderMap.keySet()) { // Make sure to process superclass before subclass. This is because if there is a class that // requires a View in the constructor, all subclasses need it as well. Deque superClasses = new ArrayDeque<>(); TypeElement superClass = getSuperClass(typeElement); while (superClass != null && !processedInThisRound.contains(superClass) && !classpathBindings.containsKey(superClass)) { superClasses.addFirst(superClass); superClass = getSuperClass(superClass); } boolean parentHasConstructorWithView = false; while (!superClasses.isEmpty()) { TypeElement superclass = superClasses.removeFirst(); ClasspathBindingSet classpathBinding = findBindingInfoForType(superclass, requireViewInConstructor, supportedAnnotations, parentHasConstructorWithView); if (classpathBinding != null) { parentHasConstructorWithView |= classpathBinding.constructorNeedsView(); classpathBindings.put(superclass, classpathBinding); } } } return ImmutableMap.copyOf(classpathBindings); } private @Nullable ClasspathBindingSet findBindingInfoForType( TypeElement typeElement, Set> requireConstructorWithView, Set> otherAnnotations, boolean needsConstructorWithView) { boolean foundSupportedAnnotation = false; for (Element enclosedElement : typeElement.getEnclosedElements()) { for (Class bindViewAnnotation : requireConstructorWithView) { if (enclosedElement.getAnnotation(bindViewAnnotation) != null) { return new ClasspathBindingSet(true, typeElement); } } for (Class supportedAnnotation : otherAnnotations) { if (enclosedElement.getAnnotation(supportedAnnotation) != null) { if (needsConstructorWithView) { return new ClasspathBindingSet(true, typeElement); } foundSupportedAnnotation = true; } } } if (foundSupportedAnnotation) { return new ClasspathBindingSet(false, typeElement); } else { return null; } } private @Nullable TypeElement getSuperClass(TypeElement typeElement) { TypeMirror type = typeElement.getSuperclass(); if (type.getKind() == TypeKind.NONE) { return null; } return (TypeElement) ((DeclaredType) type).asElement(); } @Override public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); } private void error(Element element, String message, Object... args) { printMessage(Kind.ERROR, element, message, args); } private void note(Element element, String message, Object... args) { printMessage(Kind.NOTE, element, message, args); } private void printMessage(Kind kind, Element element, String message, Object[] args) { if (args.length > 0) { message = String.format(message, args); } processingEnv.getMessager().printMessage(kind, message, element); } private Id elementToId(Element element, Class annotation, int value) { JCTree tree = (JCTree) trees.getTree(element, getMirror(element, annotation)); if (tree != null) { // tree can be null if the references are compiled types and not source rScanner.reset(); tree.accept(rScanner); if (!rScanner.resourceIds.isEmpty()) { return rScanner.resourceIds.values().iterator().next(); } } return new Id(value); } private Map elementToIds(Element element, Class annotation, int[] values) { Map resourceIds = new LinkedHashMap<>(); JCTree tree = (JCTree) trees.getTree(element, getMirror(element, annotation)); if (tree != null) { // tree can be null if the references are compiled types and not source rScanner.reset(); tree.accept(rScanner); resourceIds = rScanner.resourceIds; } // Every value looked up should have an Id for (int value : values) { resourceIds.putIfAbsent(value, new Id(value)); } return resourceIds; } private static boolean hasAnnotationWithName(Element element, String simpleName) { for (AnnotationMirror mirror : element.getAnnotationMirrors()) { String annotationName = mirror.getAnnotationType().asElement().getSimpleName().toString(); if (simpleName.equals(annotationName)) { return true; } } return false; } private static boolean isFieldRequired(Element element) { return !hasAnnotationWithName(element, NULLABLE_ANNOTATION_NAME); } private static boolean isListenerRequired(ExecutableElement element) { return element.getAnnotation(Optional.class) == null; } private static @Nullable AnnotationMirror getMirror(Element element, Class annotation) { for (AnnotationMirror annotationMirror : element.getAnnotationMirrors()) { if (annotationMirror.getAnnotationType().toString().equals(annotation.getCanonicalName())) { return annotationMirror; } } return null; } private static class RScanner extends TreeScanner { Map resourceIds = new LinkedHashMap<>(); @Override public void visitIdent(JCTree.JCIdent jcIdent) { super.visitIdent(jcIdent); Symbol symbol = jcIdent.sym; if (symbol.type instanceof Type.JCPrimitiveType) { Id id = parseId(symbol); if (id != null) { resourceIds.put(id.value, id); } } } @Override public void visitSelect(JCTree.JCFieldAccess jcFieldAccess) { Symbol symbol = jcFieldAccess.sym; Id id = parseId(symbol); if (id != null) { resourceIds.put(id.value, id); } } @Nullable private Id parseId(Symbol symbol) { Id id = null; if (symbol.getEnclosingElement() != null && symbol.getEnclosingElement().getEnclosingElement() != null && symbol.getEnclosingElement().getEnclosingElement().enclClass() != null) { try { int value = (Integer) requireNonNull(((Symbol.VarSymbol) symbol).getConstantValue()); id = new Id(value, symbol); } catch (Exception ignored) { } } return id; } @Override public void visitLiteral(JCTree.JCLiteral jcLiteral) { try { int value = (Integer) jcLiteral.value; resourceIds.put(value, new Id(value)); } catch (Exception ignored) { } } void reset() { resourceIds.clear(); } } } ================================================ FILE: butterknife-compiler/src/main/java/butterknife/compiler/FieldAnimationBinding.java ================================================ package butterknife.compiler; import com.squareup.javapoet.CodeBlock; import static butterknife.compiler.BindingSet.ANIMATION_UTILS; final class FieldAnimationBinding implements ResourceBinding { private final Id id; private final String name; FieldAnimationBinding(Id id, String name) { this.id = id; this.name = name; } @Override public Id id() { return id; } @Override public boolean requiresResources(int sdk) { return false; } @Override public CodeBlock render(int sdk) { return CodeBlock.of("target.$L = $T.loadAnimation(context, $L)", name, ANIMATION_UTILS, id.code); } } ================================================ FILE: butterknife-compiler/src/main/java/butterknife/compiler/FieldCollectionViewBinding.java ================================================ package butterknife.compiler; import com.squareup.javapoet.CodeBlock; import com.squareup.javapoet.ParameterizedTypeName; import com.squareup.javapoet.TypeName; import java.util.List; import static butterknife.compiler.BindingSet.UTILS; import static butterknife.compiler.BindingSet.requiresCast; final class FieldCollectionViewBinding { enum Kind { ARRAY("arrayFilteringNull"), LIST("listFilteringNull"); final String factoryName; Kind(String factoryName) { this.factoryName = factoryName; } } final String name; private final TypeName type; private final Kind kind; private final boolean required; private final List ids; FieldCollectionViewBinding(String name, TypeName type, Kind kind, List ids, boolean required) { this.name = name; this.type = type; this.kind = kind; this.ids = ids; this.required = required; } CodeBlock render(boolean debuggable) { CodeBlock.Builder builder = CodeBlock.builder() .add("target.$L = $T.$L(", name, UTILS, kind.factoryName); for (int i = 0; i < ids.size(); i++) { if (i > 0) { builder.add(", "); } builder.add("\n"); Id id = ids.get(i); boolean requiresCast = requiresCast(type); if (!debuggable) { if (requiresCast) { builder.add("($T) ", type); } builder.add("source.findViewById($L)", id.code); } else if (!requiresCast && !required) { builder.add("source.findViewById($L)", id.code); } else { builder.add("$T.find", UTILS); builder.add(required ? "RequiredView" : "OptionalView"); if (requiresCast) { builder.add("AsType"); } builder.add("(source, $L, \"field '$L'\"", id.code, name); if (requiresCast) { TypeName rawType = type; if (rawType instanceof ParameterizedTypeName) { rawType = ((ParameterizedTypeName) rawType).rawType; } builder.add(", $T.class", rawType); } builder.add(")"); } } return builder.add(")").build(); } } ================================================ FILE: butterknife-compiler/src/main/java/butterknife/compiler/FieldDrawableBinding.java ================================================ package butterknife.compiler; import com.squareup.javapoet.CodeBlock; import static butterknife.compiler.BindingSet.CONTEXT_COMPAT; import static butterknife.compiler.BindingSet.UTILS; import static butterknife.internal.Constants.NO_RES_ID; final class FieldDrawableBinding implements ResourceBinding { private final Id id; private final String name; private final Id tintAttributeId; FieldDrawableBinding(Id id, String name, Id tintAttributeId) { this.id = id; this.name = name; this.tintAttributeId = tintAttributeId; } @Override public Id id() { return id; } @Override public boolean requiresResources(int sdk) { return false; } @Override public CodeBlock render(int sdk) { if (tintAttributeId.value != NO_RES_ID) { return CodeBlock.of("target.$L = $T.getTintedDrawable(context, $L, $L)", name, UTILS, id.code, tintAttributeId.code); } if (sdk >= 21) { return CodeBlock.of("target.$L = context.getDrawable($L)", name, id.code); } return CodeBlock.of("target.$L = $T.getDrawable(context, $L)", name, CONTEXT_COMPAT, id.code); } } ================================================ FILE: butterknife-compiler/src/main/java/butterknife/compiler/FieldResourceBinding.java ================================================ package butterknife.compiler; import androidx.annotation.Nullable; import com.google.common.collect.ImmutableList; import com.google.errorprone.annotations.Immutable; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; import java.util.ArrayList; import java.util.Collections; import java.util.List; final class FieldResourceBinding implements ResourceBinding { enum Type { BITMAP(new ResourceMethod(BindingSet.BITMAP_FACTORY, "decodeResource", true, 1)), BOOL("getBoolean"), COLOR(new ResourceMethod(BindingSet.CONTEXT_COMPAT, "getColor", false, 1), new ResourceMethod(null, "getColor", false, 23)), COLOR_STATE_LIST(new ResourceMethod(BindingSet.CONTEXT_COMPAT, "getColorStateList", false, 1), new ResourceMethod(null, "getColorStateList", false, 23)), DIMEN_AS_INT("getDimensionPixelSize"), DIMEN_AS_FLOAT("getDimension"), FLOAT(new ResourceMethod(BindingSet.UTILS, "getFloat", false, 1)), INT("getInteger"), INT_ARRAY("getIntArray"), STRING("getString"), STRING_ARRAY("getStringArray"), TEXT_ARRAY("getTextArray"), TYPED_ARRAY("obtainTypedArray"); private final ImmutableList methods; Type(ResourceMethod... methods) { List methodList = new ArrayList<>(methods.length); Collections.addAll(methodList, methods); Collections.sort(methodList); Collections.reverse(methodList); this.methods = ImmutableList.copyOf(methodList); } Type(String methodName) { methods = ImmutableList.of(new ResourceMethod(null, methodName, true, 1)); } ResourceMethod methodForSdk(int sdk) { for (ResourceMethod method : methods) { if (method.sdk <= sdk) { return method; } } throw new AssertionError(); } } @Immutable static final class ResourceMethod implements Comparable { @SuppressWarnings("Immutable") final @Nullable ClassName typeName; final String name; final boolean requiresResources; final int sdk; ResourceMethod(@Nullable ClassName typeName, String name, boolean requiresResources, int sdk) { this.typeName = typeName; this.name = name; this.requiresResources = requiresResources; this.sdk = sdk; } @Override public int compareTo(ResourceMethod other) { return Integer.compare(sdk, other.sdk); } } private final Id id; private final String name; private final Type type; FieldResourceBinding(Id id, String name, Type type) { this.id = id; this.name = name; this.type = type; } @Override public Id id() { return id; } @Override public boolean requiresResources(int sdk) { return type.methodForSdk(sdk).requiresResources; } @Override public CodeBlock render(int sdk) { ResourceMethod method = type.methodForSdk(sdk); if (method.typeName == null) { if (method.requiresResources) { return CodeBlock.of("target.$L = res.$L($L)", name, method.name, id.code); } return CodeBlock.of("target.$L = context.$L($L)", name, method.name, id.code); } if (method.requiresResources) { return CodeBlock.of("target.$L = $T.$L(res, $L)", name, method.typeName, method.name, id.code); } return CodeBlock.of("target.$L = $T.$L(context, $L)", name, method.typeName, method.name, id.code); } } ================================================ FILE: butterknife-compiler/src/main/java/butterknife/compiler/FieldTypefaceBinding.java ================================================ package butterknife.compiler; import androidx.annotation.Nullable; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; final class FieldTypefaceBinding implements ResourceBinding { private static final ClassName RESOURCES_COMPAT = ClassName.get("androidx.core.content.res", "ResourcesCompat"); private static final ClassName TYPEFACE = ClassName.get("android.graphics", "Typeface"); /** Keep in sync with {@link android.graphics.Typeface} constants. */ enum TypefaceStyles { NORMAL(0), BOLD(1), ITALIC(2), BOLD_ITALIC(3); final int value; TypefaceStyles(int value) { this.value = value; } @Nullable static TypefaceStyles fromValue(int value) { for (TypefaceStyles style : values()) { if (style.value == value) { return style; } } return null; } } private final Id id; private final String name; private final TypefaceStyles style; FieldTypefaceBinding(Id id, String name, TypefaceStyles style) { this.id = id; this.name = name; this.style = style; } @Override public Id id() { return id; } @Override public boolean requiresResources(int sdk) { return sdk >= 26; } @Override public CodeBlock render(int sdk) { CodeBlock typeface = sdk >= 26 ? CodeBlock.of("res.getFont($L)", id.code) : CodeBlock.of("$T.getFont(context, $L)", RESOURCES_COMPAT, id.code); if (style != TypefaceStyles.NORMAL) { typeface = CodeBlock.of("$1T.create($2L, $1T.$3L)", TYPEFACE, typeface, style); } return CodeBlock.of("target.$L = $L", name, typeface); } } ================================================ FILE: butterknife-compiler/src/main/java/butterknife/compiler/FieldViewBinding.java ================================================ package butterknife.compiler; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.ParameterizedTypeName; import com.squareup.javapoet.TypeName; final class FieldViewBinding implements MemberViewBinding { private final String name; private final TypeName type; private final boolean required; FieldViewBinding(String name, TypeName type, boolean required) { this.name = name; this.type = type; this.required = required; } public String getName() { return name; } public TypeName getType() { return type; } public ClassName getRawType() { if (type instanceof ParameterizedTypeName) { return ((ParameterizedTypeName) type).rawType; } return (ClassName) type; } @Override public String getDescription() { return "field '" + name + "'"; } public boolean isRequired() { return required; } } ================================================ FILE: butterknife-compiler/src/main/java/butterknife/compiler/Id.java ================================================ package butterknife.compiler; import androidx.annotation.Nullable; import com.squareup.javapoet.ClassName; import com.squareup.javapoet.CodeBlock; import com.sun.tools.javac.code.Symbol; /** * Represents an ID of an Android resource. */ final class Id { private static final ClassName ANDROID_R = ClassName.get("android", "R"); private static final String R = "R"; final int value; final CodeBlock code; final boolean qualifed; Id(int value) { this(value, null); } Id(int value, @Nullable Symbol rSymbol) { this.value = value; if (rSymbol != null) { ClassName className = ClassName.get(rSymbol.packge().getQualifiedName().toString(), R, rSymbol.enclClass().name.toString()); String resourceName = rSymbol.name.toString(); this.code = className.topLevelClassName().equals(ANDROID_R) ? CodeBlock.of("$L.$N", className, resourceName) : CodeBlock.of("$T.$N", className, resourceName); this.qualifed = true; } else { this.code = CodeBlock.of("$L", value); this.qualifed = false; } } @Override public boolean equals(Object o) { return o instanceof Id && value == ((Id) o).value; } @Override public int hashCode() { return value; } @Override public String toString() { throw new UnsupportedOperationException("Please use value or code explicitly"); } } ================================================ FILE: butterknife-compiler/src/main/java/butterknife/compiler/MemberViewBinding.java ================================================ package butterknife.compiler; /** A field or method view binding. */ interface MemberViewBinding { /** A description of the binding in human readable form (e.g., "field 'foo'"). */ String getDescription(); } ================================================ FILE: butterknife-compiler/src/main/java/butterknife/compiler/MethodViewBinding.java ================================================ package butterknife.compiler; import java.util.ArrayList; import java.util.Collections; import java.util.List; final class MethodViewBinding implements MemberViewBinding { private final String name; private final List parameters; private final boolean required; private final boolean hasReturnValue; MethodViewBinding(String name, List parameters, boolean required, boolean hasReturnValue) { this.name = name; this.parameters = Collections.unmodifiableList(new ArrayList<>(parameters)); this.required = required; this.hasReturnValue = hasReturnValue; } public String getName() { return name; } public List getParameters() { return parameters; } @Override public String getDescription() { return "method '" + name + "'"; } public boolean isRequired() { return required; } public boolean hasReturnValue() { return hasReturnValue; } } ================================================ FILE: butterknife-compiler/src/main/java/butterknife/compiler/Parameter.java ================================================ package butterknife.compiler; import com.squareup.javapoet.TypeName; /** Represents a parameter type and its position in the listener method. */ final class Parameter { static final Parameter[] NONE = new Parameter[0]; private final int listenerPosition; private final TypeName type; Parameter(int listenerPosition, TypeName type) { this.listenerPosition = listenerPosition; this.type = type; } int getListenerPosition() { return listenerPosition; } TypeName getType() { return type; } public boolean requiresCast(String toType) { return !type.toString().equals(toType); } } ================================================ FILE: butterknife-compiler/src/main/java/butterknife/compiler/ResourceBinding.java ================================================ package butterknife.compiler; import com.squareup.javapoet.CodeBlock; interface ResourceBinding { Id id(); /** True if the code for this binding requires a 'res' variable for {@code Resources} access. */ boolean requiresResources(int sdk); CodeBlock render(int sdk); } ================================================ FILE: butterknife-compiler/src/main/java/butterknife/compiler/ViewBinding.java ================================================ package butterknife.compiler; import butterknife.internal.ListenerClass; import butterknife.internal.ListenerMethod; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.annotation.Nullable; final class ViewBinding { private final Id id; private final Map>> methodBindings; private final @Nullable FieldViewBinding fieldBinding; ViewBinding(Id id, Map>> methodBindings, @Nullable FieldViewBinding fieldBinding) { this.id = id; this.methodBindings = methodBindings; this.fieldBinding = fieldBinding; } public Id getId() { return id; } public @Nullable FieldViewBinding getFieldBinding() { return fieldBinding; } public Map>> getMethodBindings() { return methodBindings; } public List getRequiredBindings() { List requiredBindings = new ArrayList<>(); if (fieldBinding != null && fieldBinding.isRequired()) { requiredBindings.add(fieldBinding); } for (Map> methodBinding : methodBindings.values()) { for (Set set : methodBinding.values()) { for (MethodViewBinding binding : set) { if (binding.isRequired()) { requiredBindings.add(binding); } } } } return requiredBindings; } public boolean isSingleFieldBinding() { return methodBindings.isEmpty() && fieldBinding != null; } public boolean requiresLocal() { if (isBoundToRoot()) { return false; } if (isSingleFieldBinding()) { return false; } return true; } public boolean isBoundToRoot() { return ButterKnifeProcessor.NO_ID.equals(id); } public static final class Builder { private final Id id; private final Map>> methodBindings = new LinkedHashMap<>(); @Nullable FieldViewBinding fieldBinding; Builder(Id id) { this.id = id; } public boolean hasMethodBinding(ListenerClass listener, ListenerMethod method) { Map> methods = methodBindings.get(listener); return methods != null && methods.containsKey(method); } public void addMethodBinding(ListenerClass listener, ListenerMethod method, MethodViewBinding binding) { Map> methods = methodBindings.get(listener); Set set = null; if (methods == null) { methods = new LinkedHashMap<>(); methodBindings.put(listener, methods); } else { set = methods.get(method); } if (set == null) { set = new LinkedHashSet<>(); methods.put(method, set); } set.add(binding); } public void setFieldBinding(FieldViewBinding fieldBinding) { if (this.fieldBinding != null) { throw new AssertionError(); } this.fieldBinding = fieldBinding; } public ViewBinding build() { return new ViewBinding(id, methodBindings, fieldBinding); } } } ================================================ FILE: butterknife-compiler/src/test/java/butterknife/compiler/BindingSetTest.java ================================================ package butterknife.compiler; import org.junit.Test; import static butterknife.compiler.BindingSet.asHumanDescription; import static com.google.common.truth.Truth.assertThat; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; public class BindingSetTest { @Test public void humanDescriptionJoinWorks() { MemberViewBinding one = new TestViewBinding("one"); MemberViewBinding two = new TestViewBinding("two"); MemberViewBinding three = new TestViewBinding("three"); String result1 = asHumanDescription(singletonList(one)); assertThat(result1).isEqualTo("one"); String result2 = asHumanDescription(asList(one, two)); assertThat(result2).isEqualTo("one and two"); String result3 = asHumanDescription(asList(one, two, three)); assertThat(result3).isEqualTo("one, two, and three"); } private static class TestViewBinding implements MemberViewBinding { private final String description; private TestViewBinding(String description) { this.description = description; } @Override public String getDescription() { return description; } } } ================================================ FILE: butterknife-gradle-plugin/build.gradle ================================================ apply plugin: 'java-gradle-plugin' apply plugin: 'kotlin' sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 dependencies { compileOnly gradleApi() implementation deps.android.gradlePlugin implementation deps.javapoet implementation deps.kotlin.stdLibJdk8 testImplementation deps.junit testImplementation deps.truth testImplementation deps.androidx.annotations testImplementation deps.compiletesting } test { dependsOn(':butterknife:installLocally') dependsOn(':butterknife-annotations:installLocally') dependsOn(':butterknife-compiler:installLocally') dependsOn(':butterknife-runtime:installLocally') systemProperty('butterknife.version', version) } apply from: rootProject.file('gradle/gradle-mvn-push.gradle') ================================================ FILE: butterknife-gradle-plugin/gradle.properties ================================================ POM_NAME=Butterknife Gradle Plugin POM_ARTIFACT_ID=butterknife-gradle-plugin POM_PACKAGING=jar ================================================ FILE: butterknife-gradle-plugin/src/main/java/butterknife/plugin/ButterKnifePlugin.kt ================================================ package butterknife.plugin import com.android.build.gradle.AppExtension import com.android.build.gradle.AppPlugin import com.android.build.gradle.FeatureExtension import com.android.build.gradle.FeaturePlugin import com.android.build.gradle.LibraryExtension import com.android.build.gradle.LibraryPlugin import com.android.build.gradle.api.BaseVariant import com.android.build.gradle.internal.res.GenerateLibraryRFileTask import com.android.build.gradle.internal.res.LinkApplicationAndroidResourcesTask import groovy.util.XmlSlurper import org.gradle.api.DomainObjectSet import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.plugins.ExtensionContainer import java.util.concurrent.atomic.AtomicBoolean import kotlin.reflect.KClass class ButterKnifePlugin : Plugin { override fun apply(project: Project) { project.plugins.all { when (it) { is FeaturePlugin -> { project.extensions[FeatureExtension::class].run { configureR2Generation(project, featureVariants) configureR2Generation(project, libraryVariants) } } is LibraryPlugin -> { project.extensions[LibraryExtension::class].run { configureR2Generation(project, libraryVariants) } } is AppPlugin -> { project.extensions[AppExtension::class].run { configureR2Generation(project, applicationVariants) } } } } } // Parse the variant's main manifest file in order to get the package id which is used to create // R.java in the right place. private fun getPackageName(variant : BaseVariant) : String { val slurper = XmlSlurper(false, false) val list = variant.sourceSets.map { it.manifestFile } // According to the documentation, the earlier files in the list are meant to be overridden by the later ones. // So the first file in the sourceSets list should be main. val result = slurper.parse(list[0]) return result.getProperty("@package").toString() } private fun configureR2Generation(project: Project, variants: DomainObjectSet) { variants.all { variant -> val outputDir = project.buildDir.resolve( "generated/source/r2/${variant.dirName}") val rPackage = getPackageName(variant) val once = AtomicBoolean() variant.outputs.all { output -> // Though there might be multiple outputs, their R files are all the same. Thus, we only // need to configure the task once with the R.java input and action. if (once.compareAndSet(false, true)) { val processResources = output.processResourcesProvider.get() // TODO lazy // TODO: switch to better API once exists in AGP (https://issuetracker.google.com/118668005) val rFile = project.files( when (processResources) { is GenerateLibraryRFileTask -> processResources.textSymbolOutputFile is LinkApplicationAndroidResourcesTask -> processResources.textSymbolOutputFile else -> throw RuntimeException( "Minimum supported Android Gradle Plugin is 3.3.0") }) .builtBy(processResources) val generate = project.tasks.create("generate${variant.name.capitalize()}R2", R2Generator::class.java) { it.outputDir = outputDir it.rFile = rFile it.packageName = rPackage it.className = "R2" } variant.registerJavaGeneratingTask(generate, outputDir) } } } } private operator fun ExtensionContainer.get(type: KClass): T { return getByType(type.java) } } ================================================ FILE: butterknife-gradle-plugin/src/main/java/butterknife/plugin/FinalRClassBuilder.kt ================================================ package butterknife.plugin import com.squareup.javapoet.ClassName import com.squareup.javapoet.CodeBlock import com.squareup.javapoet.FieldSpec import com.squareup.javapoet.JavaFile import com.squareup.javapoet.TypeSpec import java.util.Locale import javax.lang.model.element.Modifier.FINAL import javax.lang.model.element.Modifier.PUBLIC import javax.lang.model.element.Modifier.STATIC private const val ANNOTATION_PACKAGE = "androidx.annotation" internal val SUPPORTED_TYPES = setOf("anim", "array", "attr", "bool", "color", "dimen", "drawable", "id", "integer", "layout", "menu", "plurals", "string", "style", "styleable") /** * Generates a class that contains all supported field names in an R file as final values. * Also enables adding support annotations to indicate the type of resource for every field. */ class FinalRClassBuilder( private val packageName: String, private val className: String ) { private var resourceTypes = mutableMapOf() fun build(): JavaFile { val result = TypeSpec.classBuilder(className) .addModifiers(PUBLIC, FINAL) for (type in SUPPORTED_TYPES) { resourceTypes.get(type)?.let { result.addType(it.build()) } } return JavaFile.builder(packageName, result.build()) .addFileComment("Generated code from Butter Knife gradle plugin. Do not modify!") .build() } fun addResourceField(type: String, fieldName: String, fieldInitializer: CodeBlock) { if (type !in SUPPORTED_TYPES) { return } val fieldSpecBuilder = FieldSpec.builder(Int::class.javaPrimitiveType, fieldName) .addModifiers(PUBLIC, STATIC, FINAL) .initializer(fieldInitializer) fieldSpecBuilder.addAnnotation(getSupportAnnotationClass(type)) val resourceType = resourceTypes.getOrPut(type) { TypeSpec.classBuilder(type).addModifiers(PUBLIC, STATIC, FINAL) } resourceType.addField(fieldSpecBuilder.build()) } private fun getSupportAnnotationClass(type: String): ClassName { return ClassName.get(ANNOTATION_PACKAGE, type.capitalize(Locale.US) + "Res") } // TODO https://youtrack.jetbrains.com/issue/KT-28933 private fun String.capitalize(locale: Locale) = substring(0, 1).toUpperCase(locale) + substring(1) } ================================================ FILE: butterknife-gradle-plugin/src/main/java/butterknife/plugin/R2Generator.kt ================================================ package butterknife.plugin import org.gradle.api.DefaultTask import org.gradle.api.file.FileCollection import org.gradle.api.tasks.CacheableTask import org.gradle.api.tasks.Input import org.gradle.api.tasks.InputFiles import org.gradle.api.tasks.OutputDirectory import org.gradle.api.tasks.PathSensitive import org.gradle.api.tasks.PathSensitivity import org.gradle.api.tasks.TaskAction import java.io.File @CacheableTask open class R2Generator : DefaultTask() { @get:OutputDirectory var outputDir: File? = null @get:InputFiles @get:PathSensitive(PathSensitivity.NONE) var rFile: FileCollection? = null @get:Input var packageName: String? = null @get:Input var className: String? = null @Suppress("unused") // Invoked by Gradle. @TaskAction fun brewJava() { brewJava(rFile!!.singleFile, outputDir!!, packageName!!, className!!) } } fun brewJava( rFile: File, outputDir: File, packageName: String, className: String ) { FinalRClassBuilder(packageName, className) .also { ResourceSymbolListReader(it).readSymbolTable(rFile) } .build() .writeTo(outputDir) } ================================================ FILE: butterknife-gradle-plugin/src/main/java/butterknife/plugin/ResourceSymbolListReader.kt ================================================ package butterknife.plugin import com.squareup.javapoet.CodeBlock import java.io.File internal class ResourceSymbolListReader(private val builder: FinalRClassBuilder) { private var idValue = 0 fun readSymbolTable(symbolTable: File) { symbolTable.forEachLine { processLine(it) } } private fun processLine(line: String) { val values = line.split(' ') if (values.size < 4) { return } val javaType = values[0] if (javaType != "int") { return } val symbolType = values[1] if (symbolType !in SUPPORTED_TYPES) { return } val name = values[2] val value = CodeBlock.of("\$L", ++idValue) builder.addResourceField(symbolType, name, value) } } ================================================ FILE: butterknife-gradle-plugin/src/main/resources/META-INF/gradle-plugins/com.jakewharton.butterknife.properties ================================================ implementation-class=butterknife.plugin.ButterKnifePlugin ================================================ FILE: butterknife-gradle-plugin/src/test/AndroidManifest.xml ================================================ ================================================ FILE: butterknife-gradle-plugin/src/test/build.gradle ================================================ plugins { id 'com.android.application' id 'com.jakewharton.butterknife' } repositories { google() maven { url "file://${projectDir.absolutePath}/../../../../../build/localMaven" } mavenCentral() } android { compileSdkVersion 28 compileOptions { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 } defaultConfig { // This is different than the manifest. applicationId 'com.example.butterknife' minSdkVersion 27 targetSdkVersion 27 versionCode 1 versionName '1.0.0' } // Add differing applicationIdSuffixes for debug and release to ensure that the gradle plugin // finds the R.java file correctly. buildTypes { debug { applicationIdSuffix = ".debug" } release { applicationIdSuffix = ".release" } } flavorDimensions "flavorA" // Override the applicationId in flavors to ensure that the gradle plugin // finds the R.java file correctly. productFlavors { flavorA { applicationId "foo.bar" } flavorB { applicationId "bar.foo" } } lintOptions { checkReleaseBuilds false } } dependencies { implementation "androidx.core:core:1.0.0" implementation "com.jakewharton:butterknife:${getProperty("butterknife.version")}" annotationProcessor "com.jakewharton:butterknife-compiler:${getProperty("butterknife.version")}" } ================================================ FILE: butterknife-gradle-plugin/src/test/fixtures/suffix_parsed_properly/src/main/java/butterknife/test/ButteryActivity.java ================================================ package butterknife.test; import android.app.Activity; import android.os.Bundle; import android.widget.TextView; import butterknife.ButterKnife; import butterknife.BindView; import com.example.butterknife.R; import com.example.butterknife.R2; class ButteryActivity extends Activity { @BindView(R2.id.title) TextView title; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ButterKnife.bind(this); } } ================================================ FILE: butterknife-gradle-plugin/src/test/fixtures/suffix_parsed_properly/src/main/res/layout/activity_layout.xml ================================================ ================================================ FILE: butterknife-gradle-plugin/src/test/java/butterknife/plugin/AndroidHome.kt ================================================ package butterknife.plugin import java.io.File import java.util.Properties internal fun androidHome(): String { val env = System.getenv("ANDROID_HOME") if (env != null) { return env } val localProp = File(File(System.getProperty("user.dir")).parentFile, "local.properties") if (localProp.exists()) { val prop = Properties() localProp.inputStream().use { prop.load(it) } val sdkHome = prop.getProperty("sdk.dir") if (sdkHome != null) { return sdkHome } } throw IllegalStateException( "Missing 'ANDROID_HOME' environment variable or local.properties with 'sdk.dir'") } ================================================ FILE: butterknife-gradle-plugin/src/test/java/butterknife/plugin/BuildFilesRule.kt ================================================ package butterknife.plugin import com.google.common.truth.Truth.assertThat import org.junit.rules.TestRule import org.junit.runner.Description import org.junit.runners.model.Statement import java.io.File class BuildFilesRule(private val root: File) : TestRule { override fun apply(base: Statement, description: Description): Statement { return object : Statement() { override fun evaluate() { val settingsFile = File(root, "settings.gradle") val hasSettingsFile = settingsFile.exists() if (!hasSettingsFile) settingsFile.writeText("") val buildFile = File(root, "build.gradle") val hasBuildFile = buildFile.exists() if (hasBuildFile) { assertThat(buildFile.readText()) } else { val buildFileTemplate = File(root, "../../build.gradle").readText() buildFile.writeText(buildFileTemplate) } val manifestFile = File(root, "src/main/AndroidManifest.xml") val hasManifestFile = manifestFile.exists() if (!hasManifestFile) { val manifestFileTemplate = File(root, "../../AndroidManifest.xml").readText() manifestFile.writeText(manifestFileTemplate) } try { base.evaluate() } finally { if (!hasSettingsFile) settingsFile.delete() if (!hasBuildFile) buildFile.delete() if (!hasManifestFile) manifestFile.delete() } } } } } ================================================ FILE: butterknife-gradle-plugin/src/test/java/butterknife/plugin/FinalRClassBuilderTest.kt ================================================ package butterknife.plugin import com.google.common.truth.Truth.assertAbout import com.google.testing.compile.JavaFileObjects import com.google.testing.compile.JavaSourceSubjectFactory.javaSource import org.junit.Assert.assertEquals import org.junit.Rule import org.junit.Test import org.junit.rules.TemporaryFolder class FinalRClassBuilderTest { @Rule @JvmField val tempFolder = TemporaryFolder() @Test fun brewJava() { val packageName = "com.butterknife.example" val rFile = tempFolder.newFile("R.txt").also { it.writeText(javaClass.getResource("/fixtures/R.txt").readText()) } val outputDir = tempFolder.newFolder() brewJava(rFile, outputDir, packageName, "R2") val actual = outputDir.resolve("com/butterknife/example/R2.java").readText() val expected = javaClass.getResource("/fixtures/R2.java").readText() assertEquals(expected.trim(), actual.trim()) val actualJava = JavaFileObjects.forSourceString("$packageName.R2", actual) assertAbout(javaSource()).that(actualJava).compilesWithoutError() } } ================================================ FILE: butterknife-gradle-plugin/src/test/java/butterknife/plugin/FixturesTest.kt ================================================ package butterknife.plugin import com.google.common.truth.Truth.assertThat import org.gradle.testkit.runner.GradleRunner import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.Parameterized import org.junit.runners.Parameterized.Parameters import java.io.File @RunWith(Parameterized::class) class FixturesTest(val fixtureRoot: File, val name: String) { @Suppress("unused") // Used by JUnit reflectively. @get:Rule val buildFilesRule = BuildFilesRule(fixtureRoot) @Test fun execute() { val androidHome = androidHome() File(fixtureRoot, "local.properties").writeText("sdk.dir=$androidHome\n") val butterKnifeVersion = System.getProperty("butterknife.version")!! val runner = GradleRunner.create() .withProjectDir(fixtureRoot) .withPluginClasspath() .withArguments("clean", "assembleDebug", "assembleRelease", "--stacktrace", "-Pbutterknife.version=$butterKnifeVersion") if (File(fixtureRoot, "ignored.txt").exists()) { println("Skipping ignored test $name.") return } val expectedFailure = File(fixtureRoot, "failure.txt") if (expectedFailure.exists()) { val result = runner.buildAndFail() for (chunk in expectedFailure.readText().split("\n\n")) { assertThat(result.output).contains(chunk) } } else { val result = runner.build() assertThat(result.output).contains("BUILD SUCCESSFUL") } } companion object { @Suppress("unused") // Used by Parameterized JUnit runner reflectively. @Parameters(name = "{1}") @JvmStatic fun parameters() = File("src/test/fixtures").listFiles() .filter { it.isDirectory } .map { arrayOf(it, it.name) } } } ================================================ FILE: butterknife-gradle-plugin/src/test/resources/fixtures/R.txt ================================================ int unsupported res 0x7f040000 int anim res 0x7f040001 int array res 0x7f040002 int attr res 0x7f040003 int bool res 0x7f040004 int color res 0x7f040005 int dimen res 0x7f040006 int drawable res 0x7f040007 int id res 0x7f040008 int integer res 0x7f040009 int layout res 0x7f040010 int menu res 0x7f040011 int plurals res 0x7f040012 int string res 0x7f040013 int style res 0x7f040014 int[] styleable resArray { 0x7f040003 , 0x7f040015 } int styleable resArray_child 0 int styleable resArray_child2 1 ================================================ FILE: butterknife-gradle-plugin/src/test/resources/fixtures/R2.java ================================================ // Generated code from Butter Knife gradle plugin. Do not modify! package com.butterknife.example; import androidx.annotation.AnimRes; import androidx.annotation.ArrayRes; import androidx.annotation.AttrRes; import androidx.annotation.BoolRes; import androidx.annotation.ColorRes; import androidx.annotation.DimenRes; import androidx.annotation.DrawableRes; import androidx.annotation.IdRes; import androidx.annotation.IntegerRes; import androidx.annotation.LayoutRes; import androidx.annotation.MenuRes; import androidx.annotation.PluralsRes; import androidx.annotation.StringRes; import androidx.annotation.StyleRes; import androidx.annotation.StyleableRes; public final class R2 { public static final class anim { @AnimRes public static final int res = 1; } public static final class array { @ArrayRes public static final int res = 2; } public static final class attr { @AttrRes public static final int res = 3; } public static final class bool { @BoolRes public static final int res = 4; } public static final class color { @ColorRes public static final int res = 5; } public static final class dimen { @DimenRes public static final int res = 6; } public static final class drawable { @DrawableRes public static final int res = 7; } public static final class id { @IdRes public static final int res = 8; } public static final class integer { @IntegerRes public static final int res = 9; } public static final class layout { @LayoutRes public static final int res = 10; } public static final class menu { @MenuRes public static final int res = 11; } public static final class plurals { @PluralsRes public static final int res = 12; } public static final class string { @StringRes public static final int res = 13; } public static final class style { @StyleRes public static final int res = 14; } public static final class styleable { @StyleableRes public static final int resArray_child = 15; @StyleableRes public static final int resArray_child2 = 16; } } ================================================ FILE: butterknife-integration-test/build.gradle ================================================ apply plugin: 'com.android.application' android { compileSdkVersion versions.compileSdk compileOptions { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 } defaultConfig { applicationId 'com.example.butterknife' minSdkVersion versions.minSdk targetSdkVersion versions.compileSdk versionCode 1 versionName '1.0.0' testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' } lintOptions { textReport true textOutput "stdout" checkAllWarnings true warningsAsErrors true disable 'UnknownNullness' showAll true explainIssues true // We run a full lint analysis as build part in CI, so skip vital checks for assemble tasks. checkReleaseBuilds false } buildTypes { debug { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'src/main/proguard.pro' testProguardFile 'src/androidTest/proguard.pro' } } productFlavors { flavorDimensions 'runtime' reflect { dimension 'runtime' } codegen { dimension 'runtime' } } testOptions { unitTests { includeAndroidResources = true } } } dependencies { reflectImplementation project(':butterknife-reflect') codegenImplementation project(':butterknife') codegenAnnotationProcessor project(':butterknife-compiler') androidTestCodegenAnnotationProcessor project(':butterknife-compiler') androidTestImplementation deps.junit androidTestImplementation deps.truth androidTestImplementation deps.androidx.test.runner androidTestImplementation deps.androidx.test.rules } ================================================ FILE: butterknife-integration-test/src/androidTest/font_licenses.txt ================================================ Copyright 2006 The Inconsolata Project Authors This Font Software is licensed under the SIL Open Font License, Version 1.1. This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL ----------------------------------------------------------- SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 ----------------------------------------------------------- PREAMBLE The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others. The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives. DEFINITIONS "Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation. "Reserved Font Name" refers to any names specified as such after the copyright statement(s). "Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s). "Modified Version" refers to any derivative made by adding to, deleting, or substituting -- in part or in whole -- any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment. "Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software. PERMISSION & CONDITIONS Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions: 1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself. 2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user. 3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users. 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission. 5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software. TERMINATION This license becomes null and void if any of the above conditions are not met. DISCLAIMER THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. ================================================ FILE: butterknife-integration-test/src/androidTest/java/com/example/butterknife/functional/BindAnimTest.java ================================================ package com.example.butterknife.functional; import android.view.View; import android.view.animation.Animation; import butterknife.BindAnim; import butterknife.ButterKnife; import butterknife.Unbinder; import org.junit.Test; import static org.junit.Assert.assertNotNull; public final class BindAnimTest { private final View tree = ViewTree.create(1); static class Target { @BindAnim(android.R.anim.fade_in) Animation actual; } @Test public void anim() { Target target = new Target(); Unbinder unbinder = ButterKnife.bind(target, tree); assertNotNull(target.actual); // Check more? unbinder.unbind(); assertNotNull(target.actual); } } ================================================ FILE: butterknife-integration-test/src/androidTest/java/com/example/butterknife/functional/BindArrayTest.java ================================================ package com.example.butterknife.functional; import android.content.Context; import android.view.View; import androidx.test.InstrumentationRegistry; import butterknife.BindArray; import butterknife.ButterKnife; import butterknife.Unbinder; import com.example.butterknife.test.R; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; public final class BindArrayTest { private final Context context = InstrumentationRegistry.getContext(); private final View tree = ViewTree.create(1); static class StringArrayTarget { @BindArray(R.array.string_one_two_three) String[] actual; } @Test public void asStringArray() { StringArrayTarget target = new StringArrayTarget(); String[] expected = context.getResources().getStringArray(R.array.string_one_two_three); Unbinder unbinder = ButterKnife.bind(target, tree); assertThat(target.actual).isEqualTo(expected); unbinder.unbind(); assertThat(target.actual).isEqualTo(expected); } static class IntArrayTarget { @BindArray(R.array.int_one_two_three) int[] actual; } @Test public void asIntArray() { IntArrayTarget target = new IntArrayTarget(); int[] expected = context.getResources().getIntArray(R.array.int_one_two_three); Unbinder unbinder = ButterKnife.bind(target, tree); assertThat(target.actual).isEqualTo(expected); unbinder.unbind(); assertThat(target.actual).isEqualTo(expected); } static class CharSequenceArrayTarget { @BindArray(R.array.int_one_two_three) CharSequence[] actual; } @Test public void asCharSequenceArray() { CharSequenceArrayTarget target = new CharSequenceArrayTarget(); CharSequence[] expected = context.getResources().getTextArray(R.array.int_one_two_three); Unbinder unbinder = ButterKnife.bind(target, tree); assertThat(target.actual).isEqualTo(expected); unbinder.unbind(); assertThat(target.actual).isEqualTo(expected); } } ================================================ FILE: butterknife-integration-test/src/androidTest/java/com/example/butterknife/functional/BindBitmapTest.java ================================================ package com.example.butterknife.functional; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.view.View; import androidx.test.InstrumentationRegistry; import butterknife.BindBitmap; import butterknife.ButterKnife; import butterknife.Unbinder; import com.example.butterknife.test.R; import org.junit.Test; import static org.junit.Assert.assertTrue; public final class BindBitmapTest { private final Context context = InstrumentationRegistry.getContext(); private final View tree = ViewTree.create(1); static class Target { @BindBitmap(R.drawable.pixel) Bitmap actual; } @Test public void asBitmap() { Target target = new Target(); Bitmap expected = BitmapFactory.decodeResource(context.getResources(), R.drawable.pixel); Unbinder unbinder = ButterKnife.bind(target, tree); assertTrue(target.actual.sameAs(expected)); unbinder.unbind(); assertTrue(target.actual.sameAs(expected)); } } ================================================ FILE: butterknife-integration-test/src/androidTest/java/com/example/butterknife/functional/BindBoolTest.java ================================================ package com.example.butterknife.functional; import android.content.Context; import android.view.View; import androidx.test.InstrumentationRegistry; import butterknife.BindBool; import butterknife.ButterKnife; import butterknife.Unbinder; import com.example.butterknife.test.R; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; public final class BindBoolTest { private final Context context = InstrumentationRegistry.getContext(); private final View tree = ViewTree.create(1); static class Target { @BindBool(R.bool.just_true) boolean actual; } @Test public void asBoolean() { Target target = new Target(); boolean expected = context.getResources().getBoolean(R.bool.just_true); Unbinder unbinder = ButterKnife.bind(target, tree); assertThat(target.actual).isEqualTo(expected); unbinder.unbind(); assertThat(target.actual).isEqualTo(expected); } } ================================================ FILE: butterknife-integration-test/src/androidTest/java/com/example/butterknife/functional/BindColorTest.java ================================================ package com.example.butterknife.functional; import android.content.Context; import android.content.res.ColorStateList; import android.view.View; import androidx.test.InstrumentationRegistry; import butterknife.BindColor; import butterknife.ButterKnife; import butterknife.Unbinder; import com.example.butterknife.test.R; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; public final class BindColorTest { private final Context context = InstrumentationRegistry.getContext(); private final View tree = ViewTree.create(1); static class IntTarget { @BindColor(R.color.red) int actual; } @Test public void asInt() { IntTarget target = new IntTarget(); int expected = context.getResources().getColor(R.color.red); Unbinder unbinder = ButterKnife.bind(target, tree); assertThat(target.actual).isEqualTo(expected); unbinder.unbind(); assertThat(target.actual).isEqualTo(expected); } static class ColorStateListTarget { @BindColor(R.color.colors) ColorStateList actual; } @Test public void asColorStateList() { ColorStateListTarget target = new ColorStateListTarget(); ColorStateList expected = context.getResources().getColorStateList(R.color.colors); Unbinder unbinder = ButterKnife.bind(target, tree); assertThat(target.actual.toString()).isEqualTo(expected.toString()); unbinder.unbind(); assertThat(target.actual.toString()).isEqualTo(expected.toString()); } } ================================================ FILE: butterknife-integration-test/src/androidTest/java/com/example/butterknife/functional/BindDimenTest.java ================================================ package com.example.butterknife.functional; import android.content.Context; import android.view.View; import androidx.test.InstrumentationRegistry; import butterknife.BindDimen; import butterknife.ButterKnife; import butterknife.Unbinder; import com.example.butterknife.test.R; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; public final class BindDimenTest { private final Context context = InstrumentationRegistry.getContext(); private final View tree = ViewTree.create(1); static class IntTarget { @BindDimen(R.dimen.twelve_point_two_dp) int actual; } @Test public void asInt() { IntTarget target = new IntTarget(); int expected = context.getResources().getDimensionPixelSize(R.dimen.twelve_point_two_dp); Unbinder unbinder = ButterKnife.bind(target, tree); assertThat(target.actual).isEqualTo(expected); unbinder.unbind(); assertThat(target.actual).isEqualTo(expected); } static class FloatTarget { @BindDimen(R.dimen.twelve_point_two_dp) float actual; } @Test public void asFloat() { FloatTarget target = new FloatTarget(); float expected = context.getResources().getDimension(R.dimen.twelve_point_two_dp); Unbinder unbinder = ButterKnife.bind(target, tree); assertThat(target.actual).isEqualTo(expected); unbinder.unbind(); assertThat(target.actual).isEqualTo(expected); } } ================================================ FILE: butterknife-integration-test/src/androidTest/java/com/example/butterknife/functional/BindDrawableTest.java ================================================ package com.example.butterknife.functional; import android.content.Context; import android.graphics.drawable.Drawable; import android.view.View; import androidx.test.InstrumentationRegistry; import butterknife.BindDrawable; import butterknife.ButterKnife; import butterknife.Unbinder; import com.example.butterknife.test.R; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; public final class BindDrawableTest { private final Context context = InstrumentationRegistry.getContext(); private final View tree = ViewTree.create(1); static class Target { @BindDrawable(R.drawable.circle) Drawable actual; } @Test public void asDrawable() { Target target = new Target(); Drawable expected = context.getResources().getDrawable(R.drawable.circle); Unbinder unbinder = ButterKnife.bind(target, tree); assertThat(target.actual.getConstantState()).isEqualTo(expected.getConstantState()); unbinder.unbind(); assertThat(target.actual.getConstantState()).isEqualTo(expected.getConstantState()); } } ================================================ FILE: butterknife-integration-test/src/androidTest/java/com/example/butterknife/functional/BindFloatTest.java ================================================ package com.example.butterknife.functional; import android.content.Context; import android.util.TypedValue; import android.view.View; import androidx.test.InstrumentationRegistry; import butterknife.BindFloat; import butterknife.ButterKnife; import butterknife.Unbinder; import com.example.butterknife.test.R; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; public final class BindFloatTest { private final Context context = InstrumentationRegistry.getContext(); private final View tree = ViewTree.create(1); static class Target { @BindFloat(R.dimen.twelve_point_two) float actual; } @Test public void asFloat() { Target target = new Target(); TypedValue value = new TypedValue(); context.getResources().getValue(R.dimen.twelve_point_two, value, true); float expected = value.getFloat(); Unbinder unbinder = ButterKnife.bind(target, tree); assertThat(target.actual).isEqualTo(expected); unbinder.unbind(); assertThat(target.actual).isEqualTo(expected); } } ================================================ FILE: butterknife-integration-test/src/androidTest/java/com/example/butterknife/functional/BindFontTest.java ================================================ package com.example.butterknife.functional; import android.content.Context; import android.graphics.Typeface; import android.view.View; import androidx.core.content.res.ResourcesCompat; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SdkSuppress; import butterknife.BindFont; import butterknife.ButterKnife; import butterknife.Unbinder; import com.example.butterknife.test.R; import org.junit.Test; import static android.graphics.Typeface.BOLD; import static com.google.common.truth.Truth.assertThat; @SdkSuppress(minSdkVersion = 24) // AndroidX problems on earlier versions public final class BindFontTest { private final Context context = InstrumentationRegistry.getContext(); private final View tree = ViewTree.create(1); static class TargetTypeface { @BindFont(R.font.inconsolata_regular) Typeface actual; } @Test public void typeface() { TargetTypeface target = new TargetTypeface(); Typeface expected = ResourcesCompat.getFont(context, R.font.inconsolata_regular); Unbinder unbinder = ButterKnife.bind(target, tree); assertThat(target.actual).isSameAs(expected); unbinder.unbind(); assertThat(target.actual).isSameAs(expected); } static class TargetStyle { @BindFont(value = R.font.inconsolata_regular, style = BOLD) Typeface actual; } @Test public void style() { TargetStyle target = new TargetStyle(); Typeface expected = Typeface.create(ResourcesCompat.getFont(context, R.font.inconsolata_regular), BOLD); Unbinder unbinder = ButterKnife.bind(target, tree); assertThat(target.actual).isSameAs(expected); unbinder.unbind(); assertThat(target.actual).isSameAs(expected); } } ================================================ FILE: butterknife-integration-test/src/androidTest/java/com/example/butterknife/functional/BindIntTest.java ================================================ package com.example.butterknife.functional; import android.content.Context; import android.view.View; import androidx.test.InstrumentationRegistry; import butterknife.BindInt; import butterknife.ButterKnife; import butterknife.Unbinder; import com.example.butterknife.test.R; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; public final class BindIntTest { private final Context context = InstrumentationRegistry.getContext(); private final View tree = ViewTree.create(1); static class Target { @BindInt(R.integer.twelve) int actual; } @Test public void asInt() { Target target = new Target(); int expected = context.getResources().getInteger(R.integer.twelve); Unbinder unbinder = ButterKnife.bind(target, tree); assertThat(target.actual).isEqualTo(expected); unbinder.unbind(); assertThat(target.actual).isEqualTo(expected); } } ================================================ FILE: butterknife-integration-test/src/androidTest/java/com/example/butterknife/functional/BindStringTest.java ================================================ package com.example.butterknife.functional; import android.content.Context; import android.view.View; import androidx.test.InstrumentationRegistry; import butterknife.BindString; import butterknife.ButterKnife; import butterknife.Unbinder; import com.example.butterknife.test.R; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; public final class BindStringTest { private final Context context = InstrumentationRegistry.getContext(); private final View tree = ViewTree.create(1); static class Target { @BindString(R.string.hey) String actual; } @Test public void simpleInt() { Target target = new Target(); String expected = context.getString(R.string.hey); Unbinder unbinder = ButterKnife.bind(target, tree); assertThat(target.actual).isEqualTo(expected); unbinder.unbind(); assertThat(target.actual).isEqualTo(expected); } } ================================================ FILE: butterknife-integration-test/src/androidTest/java/com/example/butterknife/functional/BindViewTest.java ================================================ package com.example.butterknife.functional; import android.view.View; import butterknife.BindView; import butterknife.ButterKnife; import butterknife.Unbinder; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; public final class BindViewTest { static class TargetView { @BindView(1) View actual; } @Test public void view() { View tree = ViewTree.create(1); View expected = tree.findViewById(1); TargetView target = new TargetView(); Unbinder unbinder = ButterKnife.bind(target, tree); assertThat(target.actual).isSameAs(expected); unbinder.unbind(); assertThat(target.actual).isNull(); } } ================================================ FILE: butterknife-integration-test/src/androidTest/java/com/example/butterknife/functional/BindViewsTest.java ================================================ package com.example.butterknife.functional; import android.view.View; import butterknife.BindViews; import butterknife.ButterKnife; import butterknife.Unbinder; import java.util.List; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; public final class BindViewsTest { static class TargetViewArray { @BindViews({1, 2, 3}) View[] actual; } @Test public void array() { View tree = ViewTree.create(1, 2, 3); View expected1 = tree.findViewById(1); View expected2 = tree.findViewById(2); View expected3 = tree.findViewById(3); TargetViewArray target = new TargetViewArray(); Unbinder unbinder = ButterKnife.bind(target, tree); assertThat(target.actual).asList().containsExactly(expected1, expected2, expected3).inOrder(); unbinder.unbind(); assertThat(target.actual).isNull(); } static class TargetViewList { @BindViews({1, 2, 3}) List actual; } @Test public void list() { View tree = ViewTree.create(1, 2, 3); View expected1 = tree.findViewById(1); View expected2 = tree.findViewById(2); View expected3 = tree.findViewById(3); TargetViewList target = new TargetViewList(); Unbinder unbinder = ButterKnife.bind(target, tree); assertThat(target.actual).containsExactly(expected1, expected2, expected3).inOrder(); unbinder.unbind(); assertThat(target.actual).isNull(); } } ================================================ FILE: butterknife-integration-test/src/androidTest/java/com/example/butterknife/functional/OnCheckedChangedTest.java ================================================ package com.example.butterknife.functional; import android.content.Context; import android.view.View; import android.view.ViewGroup; import android.widget.CompoundButton; import android.widget.FrameLayout; import android.widget.ToggleButton; import androidx.test.InstrumentationRegistry; import androidx.test.annotation.UiThreadTest; import butterknife.ButterKnife; import butterknife.OnCheckedChanged; import butterknife.Optional; import butterknife.Unbinder; import com.example.butterknife.BuildConfig; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertSame; import static org.junit.Assume.assumeFalse; @SuppressWarnings("unused") // Used reflectively / by code gen. public final class OnCheckedChangedTest { static final class Simple { int clicks = 0; @OnCheckedChanged(1) void click() { clicks++; } } @UiThreadTest @Test public void simple() { View tree = ViewTree.create(ToggleButton.class, 1); View view1 = tree.findViewById(1); Simple target = new Simple(); Unbinder unbinder = ButterKnife.bind(target, tree); assertEquals(0, target.clicks); view1.performClick(); assertEquals(1, target.clicks); unbinder.unbind(); view1.performClick(); assertEquals(1, target.clicks); } static final class MultipleBindings { int clicks = 0; @OnCheckedChanged(1) void click1() { clicks++; } @OnCheckedChanged(1) void clicks2() { clicks++; } } @UiThreadTest @Test public void multipleBindings() { assumeFalse("Not implemented", BuildConfig.FLAVOR.equals("reflect")); // TODO View tree = ViewTree.create(ToggleButton.class, 1); View view1 = tree.findViewById(1); MultipleBindings target = new MultipleBindings(); Unbinder unbinder = ButterKnife.bind(target, tree); assertEquals(0, target.clicks); view1.performClick(); assertEquals(2, target.clicks); unbinder.unbind(); view1.performClick(); assertEquals(2, target.clicks); } static final class Visibilities { int clicks = 0; @OnCheckedChanged(1) public void publicClick() { clicks++; } @OnCheckedChanged(2) void packageClick() { clicks++; } @OnCheckedChanged(3) protected void protectedClick() { clicks++; } } @UiThreadTest @Test public void visibilities() { View tree = ViewTree.create(ToggleButton.class, 1, 2, 3); View view1 = tree.findViewById(1); View view2 = tree.findViewById(2); View view3 = tree.findViewById(3); Visibilities target = new Visibilities(); ButterKnife.bind(target, tree); assertEquals(0, target.clicks); view1.performClick(); assertEquals(1, target.clicks); view2.performClick(); assertEquals(2, target.clicks); view3.performClick(); assertEquals(3, target.clicks); } static final class MultipleIds { int clicks = 0; @OnCheckedChanged({1, 2}) void click() { clicks++; } } @UiThreadTest @Test public void multipleIds() { View tree = ViewTree.create(ToggleButton.class, 1, 2); View view1 = tree.findViewById(1); View view2 = tree.findViewById(2); MultipleIds target = new MultipleIds(); Unbinder unbinder = ButterKnife.bind(target, tree); assertEquals(0, target.clicks); view1.performClick(); assertEquals(1, target.clicks); view2.performClick(); assertEquals(2, target.clicks); unbinder.unbind(); view1.performClick(); view2.performClick(); assertEquals(2, target.clicks); } static final class OptionalId { int clicks = 0; @Optional @OnCheckedChanged(1) public void click() { clicks++; } } @UiThreadTest @Test public void optionalIdPresent() { View tree = ViewTree.create(ToggleButton.class, 1); View view1 = tree.findViewById(1); OptionalId target = new OptionalId(); Unbinder unbinder = ButterKnife.bind(target, tree); assertEquals(0, target.clicks); view1.performClick(); assertEquals(1, target.clicks); unbinder.unbind(); view1.performClick(); assertEquals(1, target.clicks); } @UiThreadTest @Test public void optionalIdAbsent() { View tree = ViewTree.create(ToggleButton.class, 2); View view2 = tree.findViewById(2); OptionalId target = new OptionalId(); Unbinder unbinder = ButterKnife.bind(target, tree); assertEquals(0, target.clicks); view2.performClick(); assertEquals(0, target.clicks); unbinder.unbind(); view2.performClick(); assertEquals(0, target.clicks); } static final class ArgumentCast { interface MyInterface {} View last; @OnCheckedChanged(1) void clickTextView(CompoundButton view) { last = view; } @OnCheckedChanged(2) void clickButton(ToggleButton view) { last = view; } @OnCheckedChanged(3) void clickMyInterface(MyInterface view) { last = (View) view; } } @UiThreadTest @Test public void argumentCast() { class MyView extends ToggleButton implements ArgumentCast.MyInterface { MyView(Context context) { super(context); } } View view1 = new MyView(InstrumentationRegistry.getContext()); view1.setId(1); View view2 = new MyView(InstrumentationRegistry.getContext()); view2.setId(2); View view3 = new MyView(InstrumentationRegistry.getContext()); view3.setId(3); ViewGroup tree = new FrameLayout(InstrumentationRegistry.getContext()); tree.addView(view1); tree.addView(view2); tree.addView(view3); ArgumentCast target = new ArgumentCast(); ButterKnife.bind(target, tree); view1.performClick(); assertSame(view1, target.last); view2.performClick(); assertSame(view2, target.last); view3.performClick(); assertSame(view3, target.last); } } ================================================ FILE: butterknife-integration-test/src/androidTest/java/com/example/butterknife/functional/OnClickTest.java ================================================ package com.example.butterknife.functional; import android.app.Instrumentation; import android.content.Context; import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.FrameLayout; import android.widget.TextView; import androidx.test.InstrumentationRegistry; import butterknife.ButterKnife; import butterknife.OnClick; import butterknife.Optional; import butterknife.Unbinder; import com.example.butterknife.BuildConfig; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertSame; import static org.junit.Assume.assumeFalse; @SuppressWarnings("unused") // Used reflectively / by code gen. public final class OnClickTest { private final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); static final class Simple { int clicks = 0; @OnClick(1) void click() { clicks++; } } @Test public void simple() { View tree = ViewTree.create(1); View view1 = tree.findViewById(1); Simple target = new Simple(); Unbinder unbinder = ButterKnife.bind(target, tree); assertEquals(0, target.clicks); instrumentation.runOnMainSync(() -> { view1.performClick(); assertEquals(1, target.clicks); }); instrumentation.runOnMainSync(() -> { unbinder.unbind(); view1.performClick(); assertEquals(1, target.clicks); }); } static final class MultipleBindings { int clicks = 0; @OnClick(1) void click1() { clicks++; } @OnClick(1) void clicks2() { clicks++; } } @Test public void multipleBindings() { assumeFalse("Not implemented", BuildConfig.FLAVOR.equals("reflect")); // TODO View tree = ViewTree.create(1); View view1 = tree.findViewById(1); MultipleBindings target = new MultipleBindings(); Unbinder unbinder = ButterKnife.bind(target, tree); assertEquals(0, target.clicks); instrumentation.runOnMainSync(() -> { view1.performClick(); assertEquals(2, target.clicks); }); instrumentation.runOnMainSync(() -> { unbinder.unbind(); view1.performClick(); assertEquals(2, target.clicks); }); } static final class Visibilities { int clicks = 0; @OnClick(1) public void publicClick() { clicks++; } @OnClick(2) void packageClick() { clicks++; } @OnClick(3) protected void protectedClick() { clicks++; } } @Test public void visibilities() { View tree = ViewTree.create(1, 2, 3); View view1 = tree.findViewById(1); View view2 = tree.findViewById(2); View view3 = tree.findViewById(3); Visibilities target = new Visibilities(); ButterKnife.bind(target, tree); assertEquals(0, target.clicks); instrumentation.runOnMainSync(() -> { view1.performClick(); assertEquals(1, target.clicks); }); instrumentation.runOnMainSync(() -> { view2.performClick(); assertEquals(2, target.clicks); }); instrumentation.runOnMainSync(() -> { view3.performClick(); assertEquals(3, target.clicks); }); } static final class MultipleIds { int clicks = 0; @OnClick({1, 2}) void click() { clicks++; } } @Test public void multipleIds() { View tree = ViewTree.create(1, 2); View view1 = tree.findViewById(1); View view2 = tree.findViewById(2); MultipleIds target = new MultipleIds(); Unbinder unbinder = ButterKnife.bind(target, tree); assertEquals(0, target.clicks); instrumentation.runOnMainSync(() -> { view1.performClick(); assertEquals(1, target.clicks); }); instrumentation.runOnMainSync(() -> { view2.performClick(); assertEquals(2, target.clicks); }); instrumentation.runOnMainSync(() -> { unbinder.unbind(); view1.performClick(); view2.performClick(); assertEquals(2, target.clicks); }); } static final class OptionalId { int clicks = 0; @Optional @OnClick(1) public void click() { clicks++; } } @Test public void optionalIdPresent() { View tree = ViewTree.create(1); View view1 = tree.findViewById(1); OptionalId target = new OptionalId(); Unbinder unbinder = ButterKnife.bind(target, tree); assertEquals(0, target.clicks); instrumentation.runOnMainSync(() -> { view1.performClick(); assertEquals(1, target.clicks); }); instrumentation.runOnMainSync(() -> { unbinder.unbind(); view1.performClick(); assertEquals(1, target.clicks); }); } @Test public void optionalIdAbsent() { View tree = ViewTree.create(2); View view2 = tree.findViewById(2); OptionalId target = new OptionalId(); Unbinder unbinder = ButterKnife.bind(target, tree); assertEquals(0, target.clicks); instrumentation.runOnMainSync(() -> { view2.performClick(); assertEquals(0, target.clicks); }); instrumentation.runOnMainSync(() -> { unbinder.unbind(); view2.performClick(); assertEquals(0, target.clicks); }); } static final class ArgumentCast { interface MyInterface {} View last; @OnClick(1) void clickView(View view) { last = view; } @OnClick(2) void clickTextView(TextView view) { last = view; } @OnClick(3) void clickButton(Button view) { last = view; } @OnClick(4) void clickMyInterface(MyInterface view) { last = (View) view; } } @Test public void argumentCast() { class MyView extends Button implements ArgumentCast.MyInterface { MyView(Context context) { super(context); } } View view1 = new MyView(InstrumentationRegistry.getContext()); view1.setId(1); View view2 = new MyView(InstrumentationRegistry.getContext()); view2.setId(2); View view3 = new MyView(InstrumentationRegistry.getContext()); view3.setId(3); View view4 = new MyView(InstrumentationRegistry.getContext()); view4.setId(4); ViewGroup tree = new FrameLayout(InstrumentationRegistry.getContext()); tree.addView(view1); tree.addView(view2); tree.addView(view3); tree.addView(view4); ArgumentCast target = new ArgumentCast(); ButterKnife.bind(target, tree); instrumentation.runOnMainSync(() -> { view1.performClick(); assertSame(view1, target.last); }); instrumentation.runOnMainSync(() -> { view2.performClick(); assertSame(view2, target.last); }); instrumentation.runOnMainSync(() -> { view3.performClick(); assertSame(view3, target.last); }); instrumentation.runOnMainSync(() -> { view4.performClick(); assertSame(view4, target.last); }); } } ================================================ FILE: butterknife-integration-test/src/androidTest/java/com/example/butterknife/functional/OnItemClickTest.java ================================================ package com.example.butterknife.functional; import android.content.Context; import android.view.View; import android.view.ViewGroup; import android.widget.AbsSpinner; import android.widget.AdapterView; import android.widget.FrameLayout; import androidx.test.InstrumentationRegistry; import androidx.test.annotation.UiThreadTest; import butterknife.ButterKnife; import butterknife.OnItemClick; import butterknife.Optional; import butterknife.Unbinder; import com.example.butterknife.BuildConfig; import com.example.butterknife.library.SimpleAdapter; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertSame; import static org.junit.Assume.assumeFalse; @SuppressWarnings("unused") // Used reflectively / by code gen. public final class OnItemClickTest { static class TestSpinner extends AbsSpinner { public TestSpinner(Context context) { super(context); setAdapter(new SimpleAdapter(context)); } void performItemClick(int position) { if (position < 0) { return; } AdapterView.OnItemClickListener listener = getOnItemClickListener(); if (listener != null) { listener.onItemClick(this, null, position, NO_ID); } } } static final class Simple { int clickedPosition = -1; @OnItemClick(1) void itemClick(int position) { clickedPosition = position; } } @UiThreadTest @Test public void simple() { View tree = ViewTree.create(TestSpinner.class, 1); TestSpinner spinner = tree.findViewById(1); Simple target = new Simple(); Unbinder unbinder = ButterKnife.bind(target, tree); assertEquals(-1, target.clickedPosition); spinner.performItemClick(0); assertEquals(0, target.clickedPosition); unbinder.unbind(); spinner.performItemClick(1); assertEquals(0, target.clickedPosition); } static final class MultipleBindings { int clickedPosition1 = -1; int clickedPosition2 = -1; @OnItemClick(1) void itemClick1(int position) { clickedPosition1 = position; } @OnItemClick(1) void itemClick2(int position) { clickedPosition2 = position; } } @UiThreadTest @Test public void multipleBindings() { assumeFalse("Not implemented", BuildConfig.FLAVOR.equals("reflect")); // TODO View tree = ViewTree.create(TestSpinner.class, 1); TestSpinner spinner = tree.findViewById(1); MultipleBindings target = new MultipleBindings(); Unbinder unbinder = ButterKnife.bind(target, tree); assertEquals(-1, target.clickedPosition1); assertEquals(-1, target.clickedPosition2); spinner.performItemClick(0); assertEquals(0, target.clickedPosition1); assertEquals(0, target.clickedPosition2); unbinder.unbind(); spinner.performItemClick(1); assertEquals(0, target.clickedPosition1); assertEquals(0, target.clickedPosition2); } static final class Visibilities { int clickedPosition = -1; @OnItemClick(1) public void publicItemClick(int position) { clickedPosition = position; } @OnItemClick(2) void packageItemClick(int position) { clickedPosition = position; } @OnItemClick(3) protected void protectedItemClick(int position) { clickedPosition = position; } } @UiThreadTest @Test public void visibilities() { View tree = ViewTree.create(TestSpinner.class, 1, 2, 3); TestSpinner spinner1 = tree.findViewById(1); TestSpinner spinner2 = tree.findViewById(2); TestSpinner spinner3 = tree.findViewById(3); Visibilities target = new Visibilities(); ButterKnife.bind(target, tree); assertEquals(-1, target.clickedPosition); spinner1.performItemClick(0); assertEquals(0, target.clickedPosition); spinner2.performItemClick(1); assertEquals(1, target.clickedPosition); spinner3.performItemClick(2); assertEquals(2, target.clickedPosition); } static final class MultipleIds { int clickedPosition = -1; @OnItemClick({1, 2}) void itemClick(int position) { clickedPosition = position; } } @UiThreadTest @Test public void multipleIds() { View tree = ViewTree.create(TestSpinner.class, 1, 2); TestSpinner spinner1 = tree.findViewById(1); TestSpinner spinner2 = tree.findViewById(2); MultipleIds target = new MultipleIds(); Unbinder unbinder = ButterKnife.bind(target, tree); assertEquals(-1, target.clickedPosition); spinner1.performItemClick(0); assertEquals(0, target.clickedPosition); spinner2.performItemClick(1); assertEquals(1, target.clickedPosition); unbinder.unbind(); spinner1.performItemClick(2); assertEquals(1, target.clickedPosition); spinner2.performItemClick(2); assertEquals(1, target.clickedPosition); } static final class OptionalId { int clickedPosition = -1; @Optional @OnItemClick(1) void itemClick(int position) { clickedPosition = position; } } @UiThreadTest @Test public void optionalIdPresent() { View tree = ViewTree.create(TestSpinner.class, 1); TestSpinner spinner = tree.findViewById(1); OptionalId target = new OptionalId(); Unbinder unbinder = ButterKnife.bind(target, tree); assertEquals(-1, target.clickedPosition); spinner.performItemClick(0); assertEquals(0, target.clickedPosition); unbinder.unbind(); spinner.performItemClick(1); assertEquals(0, target.clickedPosition); } @UiThreadTest @Test public void optionalIdAbsent() { View tree = ViewTree.create(TestSpinner.class, 2); TestSpinner spinner = tree.findViewById(2); OptionalId target = new OptionalId(); Unbinder unbinder = ButterKnife.bind(target, tree); assertEquals(-1, target.clickedPosition); spinner.performItemClick(0); assertEquals(-1, target.clickedPosition); unbinder.unbind(); spinner.performItemClick(0); assertEquals(-1, target.clickedPosition); } static final class ArgumentCast { interface MyInterface {} View last; @OnItemClick(1) void itemClickAdapterView(AdapterView view) { last = view; } @OnItemClick(2) void itemClickAbsSpinner(AbsSpinner view) { last = view; } @OnItemClick(3) void itemClickMyInterface(ArgumentCast.MyInterface view) { last = (View) view; } } @UiThreadTest @Test public void argumentCast() { class MySpinner extends TestSpinner implements ArgumentCast.MyInterface { MySpinner(Context context) { super(context); } } Context context = InstrumentationRegistry.getContext(); TestSpinner spinner1 = new MySpinner(context); spinner1.setId(1); TestSpinner spinner2 = new MySpinner(context); spinner2.setId(2); TestSpinner spinner3 = new MySpinner(context); spinner3.setId(3); ViewGroup tree = new FrameLayout(context); tree.addView(spinner1); tree.addView(spinner2); tree.addView(spinner3); ArgumentCast target = new ArgumentCast(); ButterKnife.bind(target, tree); spinner1.performItemClick(0); assertSame(spinner1, target.last); spinner2.performItemClick(0); assertSame(spinner2, target.last); spinner3.performItemClick(0); assertSame(spinner3, target.last); } } ================================================ FILE: butterknife-integration-test/src/androidTest/java/com/example/butterknife/functional/OnItemLongClickTest.java ================================================ package com.example.butterknife.functional; import android.content.Context; import android.view.View; import android.view.ViewGroup; import android.widget.AbsSpinner; import android.widget.AdapterView; import android.widget.FrameLayout; import androidx.test.InstrumentationRegistry; import androidx.test.annotation.UiThreadTest; import butterknife.ButterKnife; import butterknife.OnItemLongClick; import butterknife.Optional; import butterknife.Unbinder; import com.example.butterknife.library.SimpleAdapter; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; @SuppressWarnings("unused") // Used reflectively / by code gen. public final class OnItemLongClickTest { static class TestSpinner extends AbsSpinner { public TestSpinner(Context context) { super(context); setAdapter(new SimpleAdapter(context)); } boolean performItemLongClick(int position) { if (position >= 0) { AdapterView.OnItemLongClickListener listener = getOnItemLongClickListener(); if (listener != null) { return listener.onItemLongClick(this, null, position, NO_ID); } } return false; } } static final class Simple { boolean returnValue = true; int clickedPosition = -1; @OnItemLongClick(1) boolean itemClick(int position) { clickedPosition = position; return returnValue; } } @UiThreadTest @Test public void simple() { View tree = ViewTree.create(TestSpinner.class, 1); TestSpinner spinner = tree.findViewById(1); Simple target = new Simple(); Unbinder unbinder = ButterKnife.bind(target, tree); assertEquals(-1, target.clickedPosition); assertTrue(spinner.performItemLongClick(0)); assertEquals(0, target.clickedPosition); target.returnValue = false; assertFalse(spinner.performItemLongClick(1)); assertEquals(1, target.clickedPosition); unbinder.unbind(); spinner.performItemLongClick(2); assertEquals(1, target.clickedPosition); } static final class ReturnVoid { int clickedPosition = -1; @OnItemLongClick(1) void itemLongClick(int position) { clickedPosition = position; } } @UiThreadTest @Test public void returnVoid() { View tree = ViewTree.create(TestSpinner.class, 1); TestSpinner spinner = tree.findViewById(1); ReturnVoid target = new ReturnVoid(); Unbinder unbinder = ButterKnife.bind(target, tree); assertEquals(-1, target.clickedPosition); assertTrue(spinner.performItemLongClick(0)); assertEquals(0, target.clickedPosition); unbinder.unbind(); spinner.performItemLongClick(1); assertEquals(0, target.clickedPosition); } static final class Visibilities { int clickedPosition = -1; @OnItemLongClick(1) public boolean publicItemLongClick(int position) { clickedPosition = position; return true; } @OnItemLongClick(2) boolean packageItemLongClick(int position) { clickedPosition = position; return true; } @OnItemLongClick(3) protected boolean protectedItemLongClick(int position) { clickedPosition = position; return true; } } @UiThreadTest @Test public void visibilities() { View tree = ViewTree.create(TestSpinner.class, 1, 2, 3); TestSpinner spinner1 = tree.findViewById(1); TestSpinner spinner2 = tree.findViewById(2); TestSpinner spinner3 = tree.findViewById(3); Visibilities target = new Visibilities(); ButterKnife.bind(target, tree); assertEquals(-1, target.clickedPosition); spinner1.performItemLongClick(0); assertEquals(0, target.clickedPosition); spinner2.performItemLongClick(1); assertEquals(1, target.clickedPosition); spinner3.performItemLongClick(2); assertEquals(2, target.clickedPosition); } static final class MultipleIds { int clickedPosition = -1; @OnItemLongClick({1, 2}) boolean itemLongClick(int position) { clickedPosition = position; return true; } } @UiThreadTest @Test public void multipleIds() { View tree = ViewTree.create(TestSpinner.class, 1, 2); TestSpinner spinner1 = tree.findViewById(1); TestSpinner spinner2 = tree.findViewById(2); MultipleIds target = new MultipleIds(); Unbinder unbinder = ButterKnife.bind(target, tree); assertEquals(-1, target.clickedPosition); spinner1.performItemLongClick(0); assertEquals(0, target.clickedPosition); spinner2.performItemLongClick(1); assertEquals(1, target.clickedPosition); unbinder.unbind(); spinner1.performItemLongClick(2); assertEquals(1, target.clickedPosition); spinner2.performItemLongClick(2); assertEquals(1, target.clickedPosition); } static final class OptionalId { int clickedPosition = -1; @Optional @OnItemLongClick(1) boolean itemLongClick(int position) { clickedPosition = position; return true; } } @UiThreadTest @Test public void optionalIdPresent() { View tree = ViewTree.create(TestSpinner.class, 1); TestSpinner spinner = tree.findViewById(1); OptionalId target = new OptionalId(); Unbinder unbinder = ButterKnife.bind(target, tree); assertEquals(-1, target.clickedPosition); spinner.performItemLongClick(0); assertEquals(0, target.clickedPosition); unbinder.unbind(); spinner.performItemLongClick(1); assertEquals(0, target.clickedPosition); } @UiThreadTest @Test public void optionalIdAbsent() { View tree = ViewTree.create(TestSpinner.class, 2); TestSpinner spinner = tree.findViewById(2); OptionalId target = new OptionalId(); Unbinder unbinder = ButterKnife.bind(target, tree); assertEquals(-1, target.clickedPosition); spinner.performItemLongClick(0); assertEquals(-1, target.clickedPosition); unbinder.unbind(); spinner.performItemLongClick(0); assertEquals(-1, target.clickedPosition); } static final class ArgumentCast { interface MyInterface {} View last; @OnItemLongClick(1) boolean itemLongClickAdapterView(AdapterView view) { last = view; return true; } @OnItemLongClick(2) boolean itemLongClickAbsSpinner(AbsSpinner view) { last = view; return true; } @OnItemLongClick(3) boolean itemLongClickMyInterface(ArgumentCast.MyInterface view) { last = (View) view; return true; } } @UiThreadTest @Test public void argumentCast() { class MySpinner extends TestSpinner implements ArgumentCast.MyInterface { MySpinner(Context context) { super(context); } } Context context = InstrumentationRegistry.getContext(); TestSpinner spinner1 = new MySpinner(context); spinner1.setId(1); TestSpinner spinner2 = new MySpinner(context); spinner2.setId(2); TestSpinner spinner3 = new MySpinner(context); spinner3.setId(3); ViewGroup tree = new FrameLayout(context); tree.addView(spinner1); tree.addView(spinner2); tree.addView(spinner3); ArgumentCast target = new ArgumentCast(); ButterKnife.bind(target, tree); spinner1.performItemLongClick(0); assertSame(spinner1, target.last); spinner2.performItemLongClick(0); assertSame(spinner2, target.last); spinner3.performItemLongClick(0); assertSame(spinner3, target.last); } } ================================================ FILE: butterknife-integration-test/src/androidTest/java/com/example/butterknife/functional/OnItemSelectedTest.java ================================================ package com.example.butterknife.functional; import android.content.Context; import android.view.View; import android.view.ViewGroup; import android.widget.AbsSpinner; import android.widget.AdapterView; import android.widget.FrameLayout; import androidx.test.InstrumentationRegistry; import androidx.test.annotation.UiThreadTest; import butterknife.ButterKnife; import butterknife.OnItemSelected; import butterknife.Optional; import butterknife.Unbinder; import com.example.butterknife.BuildConfig; import com.example.butterknife.library.SimpleAdapter; import org.junit.Before; import org.junit.Test; import static butterknife.OnItemSelected.Callback.NOTHING_SELECTED; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertSame; import static org.junit.Assume.assumeFalse; @SuppressWarnings("unused") // Used by code gen. public final class OnItemSelectedTest { static class TestSpinner extends AbsSpinner { public TestSpinner(Context context) { super(context); setAdapter(new SimpleAdapter(context)); } void performSelection(int position) { if (position < 0) { return; } AdapterView.OnItemSelectedListener listener = getOnItemSelectedListener(); if (listener != null) { listener.onItemSelected(this, null, position, NO_ID); } } void clearSelection() { AdapterView.OnItemSelectedListener listener = getOnItemSelectedListener(); if (listener != null) { listener.onNothingSelected(this); } } } @Before public void ignoreIfReflect() { assumeFalse("Not implemented", BuildConfig.FLAVOR.equals("reflect")); // TODO } static final class Simple { int selectedPosition = -1; @OnItemSelected(1) void select(int position) { selectedPosition = position; } @OnItemSelected(value = 1, callback = NOTHING_SELECTED) void clear() { selectedPosition = -1; } } @UiThreadTest @Test public void simple() { View tree = ViewTree.create(TestSpinner.class, 1); TestSpinner spinner = tree.findViewById(1); Simple target = new Simple(); Unbinder unbinder = ButterKnife.bind(target, tree); assertEquals(-1, target.selectedPosition); spinner.performSelection(0); assertEquals(0, target.selectedPosition); spinner.clearSelection(); assertEquals(-1, target.selectedPosition); spinner.performSelection(1); unbinder.unbind(); spinner.performSelection(0); assertEquals(1, target.selectedPosition); spinner.clearSelection(); assertEquals(1, target.selectedPosition); } static final class MultipleBindings { int selectedPosition1 = -1; int selectedPosition2 = -1; @OnItemSelected(1) void select1(int position) { selectedPosition1 = position; } @OnItemSelected(1) void select2(int position) { selectedPosition2 = position; } @OnItemSelected(value = 1, callback = NOTHING_SELECTED) void clear1() { selectedPosition1 = -1; } @OnItemSelected(value = 1, callback = NOTHING_SELECTED) void clear2() { selectedPosition2 = -1; } } @UiThreadTest @Test public void multipleBindings() { View tree = ViewTree.create(TestSpinner.class, 1); TestSpinner spinner = tree.findViewById(1); MultipleBindings target = new MultipleBindings(); Unbinder unbinder = ButterKnife.bind(target, tree); assertEquals(-1, target.selectedPosition1); assertEquals(-1, target.selectedPosition2); spinner.performSelection(0); assertEquals(0, target.selectedPosition1); assertEquals(0, target.selectedPosition2); spinner.clearSelection(); assertEquals(-1, target.selectedPosition1); assertEquals(-1, target.selectedPosition2); spinner.performSelection(1); unbinder.unbind(); spinner.performSelection(0); assertEquals(1, target.selectedPosition1); assertEquals(1, target.selectedPosition2); spinner.clearSelection(); assertEquals(1, target.selectedPosition1); assertEquals(1, target.selectedPosition2); } static final class Visibilities { int selectedPosition = -1; @OnItemSelected(1) public void publicSelect(int position) { selectedPosition = position; } @OnItemSelected(2) void packageSelect(int position) { selectedPosition = position; } @OnItemSelected(3) protected void protectedSelect(int position) { selectedPosition = position; } @OnItemSelected(value = 1, callback = NOTHING_SELECTED) public void publicClear() { selectedPosition = -1; } @OnItemSelected(value = 2, callback = NOTHING_SELECTED) void packageClear() { selectedPosition = -1; } @OnItemSelected(value = 3, callback = NOTHING_SELECTED) protected void protectedClear() { selectedPosition = -1; } } @UiThreadTest @Test public void visibilities() { View tree = ViewTree.create(TestSpinner.class, 1, 2, 3); TestSpinner spinner1 = tree.findViewById(1); TestSpinner spinner2 = tree.findViewById(2); TestSpinner spinner3 = tree.findViewById(3); Visibilities target = new Visibilities(); ButterKnife.bind(target, tree); assertEquals(-1, target.selectedPosition); spinner1.performSelection(0); assertEquals(0, target.selectedPosition); spinner1.clearSelection(); assertEquals(-1, target.selectedPosition); spinner2.performSelection(0); assertEquals(0, target.selectedPosition); spinner2.clearSelection(); assertEquals(-1, target.selectedPosition); spinner3.performSelection(0); assertEquals(0, target.selectedPosition); spinner3.clearSelection(); assertEquals(-1, target.selectedPosition); } static final class MultipleIdPermutation { int selectedPosition = -1; @OnItemSelected({1, 2}) void select(int position) { selectedPosition = position; } @OnItemSelected(value = {1, 3}, callback = NOTHING_SELECTED) void clear() { selectedPosition = -1; } } @UiThreadTest @Test public void multipleIdPermutation() { View tree = ViewTree.create(TestSpinner.class, 1, 2, 3); TestSpinner spinner1 = tree.findViewById(1); TestSpinner spinner2 = tree.findViewById(2); TestSpinner spinner3 = tree.findViewById(3); MultipleIdPermutation target = new MultipleIdPermutation(); Unbinder unbinder = ButterKnife.bind(target, tree); assertEquals(-1, target.selectedPosition); spinner1.performSelection(0); assertEquals(0, target.selectedPosition); spinner1.clearSelection(); assertEquals(-1, target.selectedPosition); spinner2.performSelection(0); assertEquals(0, target.selectedPosition); spinner2.clearSelection(); assertEquals(0, target.selectedPosition); spinner3.performSelection(1); assertEquals(0, target.selectedPosition); spinner3.clearSelection(); assertEquals(-1, target.selectedPosition); spinner1.performSelection(1); unbinder.unbind(); spinner1.performSelection(0); assertEquals(1, target.selectedPosition); spinner2.performSelection(0); assertEquals(1, target.selectedPosition); spinner3.performSelection(0); assertEquals(1, target.selectedPosition); spinner1.clearSelection(); assertEquals(1, target.selectedPosition); spinner2.clearSelection(); assertEquals(1, target.selectedPosition); spinner3.clearSelection(); assertEquals(1, target.selectedPosition); } static final class OptionalId { int selectedPosition = -1; @Optional @OnItemSelected(1) void select(int position) { selectedPosition = position; } @Optional @OnItemSelected(value = 1, callback = NOTHING_SELECTED) void clear() { selectedPosition = -1; } } @UiThreadTest @Test public void optionalIdPresent() { View tree = ViewTree.create(TestSpinner.class, 1); TestSpinner spinner = tree.findViewById(1); OptionalId target = new OptionalId(); Unbinder unbinder = ButterKnife.bind(target, tree); assertEquals(-1, target.selectedPosition); spinner.performSelection(0); assertEquals(0, target.selectedPosition); spinner.clearSelection(); assertEquals(-1, target.selectedPosition); spinner.performSelection(1); unbinder.unbind(); spinner.performSelection(0); assertEquals(1, target.selectedPosition); spinner.clearSelection(); assertEquals(1, target.selectedPosition); } @UiThreadTest @Test public void optionalIdAbsent() { View tree = ViewTree.create(TestSpinner.class, 2); TestSpinner spinner = tree.findViewById(2); OptionalId target = new OptionalId(); target.selectedPosition = 1; Unbinder unbinder = ButterKnife.bind(target, tree); assertEquals(1, target.selectedPosition); spinner.performSelection(0); assertEquals(1, target.selectedPosition); spinner.clearSelection(); assertEquals(1, target.selectedPosition); unbinder.unbind(); spinner.performSelection(1); assertEquals(1, target.selectedPosition); spinner.clearSelection(); assertEquals(1, target.selectedPosition); } static final class ArgumentCast { interface MyInterface {} View last; @OnItemSelected(1) void selectAdapterView(AdapterView view) { last = view; } @OnItemSelected(2) void selectAbsSpinner(AbsSpinner view) { last = view; } @OnItemSelected(3) void selectMyInterface(MyInterface view) { last = (View) view; } @OnItemSelected(value = 1, callback = NOTHING_SELECTED) void clearAdapterView(AdapterView view) { last = view; } @OnItemSelected(value = 2, callback = NOTHING_SELECTED) void clearAbsSpinner(AbsSpinner view) { last = view; } @OnItemSelected(value = 3, callback = NOTHING_SELECTED) void clearMyInterface(MyInterface view) { last = (View) view; } } @UiThreadTest @Test public void argumentCast() { class MySpinner extends TestSpinner implements ArgumentCast.MyInterface { MySpinner(Context context) { super(context); } } Context context = InstrumentationRegistry.getContext(); TestSpinner spinner1 = new MySpinner(context); spinner1.setId(1); TestSpinner spinner2 = new MySpinner(context); spinner2.setId(2); TestSpinner spinner3 = new MySpinner(context); spinner3.setId(3); ViewGroup tree = new FrameLayout(context); tree.addView(spinner1); tree.addView(spinner2); tree.addView(spinner3); ArgumentCast target = new ArgumentCast(); ButterKnife.bind(target, tree); spinner1.performSelection(0); assertSame(spinner1, target.last); spinner2.performSelection(0); assertSame(spinner2, target.last); spinner3.performSelection(0); assertSame(spinner3, target.last); spinner1.clearSelection(); assertSame(spinner1, target.last); spinner2.clearSelection(); assertSame(spinner2, target.last); spinner3.clearSelection(); assertSame(spinner3, target.last); } } ================================================ FILE: butterknife-integration-test/src/androidTest/java/com/example/butterknife/functional/OnLongClickTest.java ================================================ package com.example.butterknife.functional; import android.content.Context; import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.FrameLayout; import android.widget.TextView; import androidx.test.InstrumentationRegistry; import androidx.test.annotation.UiThreadTest; import butterknife.ButterKnife; import butterknife.OnLongClick; import butterknife.Optional; import butterknife.Unbinder; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; @SuppressWarnings("unused") // Used reflectively / by code gen. public final class OnLongClickTest { static final class Simple { boolean returnValue = true; int clicks = 0; @OnLongClick(1) boolean click() { clicks++; return returnValue; } } @UiThreadTest @Test public void simple() { View tree = ViewTree.create(1); View view1 = tree.findViewById(1); Simple target = new Simple(); Unbinder unbinder = ButterKnife.bind(target, tree); assertEquals(0, target.clicks); assertTrue(view1.performLongClick()); assertEquals(1, target.clicks); target.returnValue = false; assertFalse(view1.performLongClick()); assertEquals(2, target.clicks); unbinder.unbind(); view1.performLongClick(); assertEquals(2, target.clicks); } static final class ReturnVoid { int clicks = 0; @OnLongClick(1) void click() { clicks++; } } @UiThreadTest @Test public void returnVoid() { View tree = ViewTree.create(1); View view1 = tree.findViewById(1); ReturnVoid target = new ReturnVoid(); Unbinder unbinder = ButterKnife.bind(target, tree); assertEquals(0, target.clicks); assertTrue(view1.performLongClick()); assertEquals(1, target.clicks); unbinder.unbind(); view1.performLongClick(); assertEquals(1, target.clicks); } static final class Visibilities { int clicks = 0; @OnLongClick(1) public boolean publicClick() { clicks++; return true; } @OnLongClick(2) boolean packageClick() { clicks++; return true; } @OnLongClick(3) protected boolean protectedClick() { clicks++; return true; } } @UiThreadTest @Test public void visibilities() { View tree = ViewTree.create(1, 2, 3); View view1 = tree.findViewById(1); View view2 = tree.findViewById(2); View view3 = tree.findViewById(3); Visibilities target = new Visibilities(); ButterKnife.bind(target, tree); assertEquals(0, target.clicks); view1.performLongClick(); assertEquals(1, target.clicks); view2.performLongClick(); assertEquals(2, target.clicks); view3.performLongClick(); assertEquals(3, target.clicks); } static final class MultipleIds { int clicks = 0; @OnLongClick({1, 2}) boolean click() { clicks++; return true; } } @UiThreadTest @Test public void multipleIds() { View tree = ViewTree.create(1, 2); View view1 = tree.findViewById(1); View view2 = tree.findViewById(2); MultipleIds target = new MultipleIds(); Unbinder unbinder = ButterKnife.bind(target, tree); assertEquals(0, target.clicks); view1.performLongClick(); assertEquals(1, target.clicks); view2.performLongClick(); assertEquals(2, target.clicks); unbinder.unbind(); view1.performLongClick(); view2.performLongClick(); assertEquals(2, target.clicks); } static final class OptionalId { int clicks = 0; @Optional @OnLongClick(1) public boolean click() { clicks++; return true; } } @UiThreadTest @Test public void optionalIdPresent() { View tree = ViewTree.create(1); View view1 = tree.findViewById(1); OptionalId target = new OptionalId(); Unbinder unbinder = ButterKnife.bind(target, tree); assertEquals(0, target.clicks); view1.performLongClick(); assertEquals(1, target.clicks); unbinder.unbind(); view1.performLongClick(); assertEquals(1, target.clicks); } @UiThreadTest @Test public void optionalIdAbsent() { View tree = ViewTree.create(2); View view2 = tree.findViewById(2); OptionalId target = new OptionalId(); Unbinder unbinder = ButterKnife.bind(target, tree); assertEquals(0, target.clicks); view2.performLongClick(); assertEquals(0, target.clicks); unbinder.unbind(); view2.performLongClick(); assertEquals(0, target.clicks); } static final class ArgumentCast { interface MyInterface {} View last; @OnLongClick(1) boolean clickView(View view) { last = view; return true; } @OnLongClick(2) boolean clickTextView(TextView view) { last = view; return true; } @OnLongClick(3) boolean clickButton(Button view) { last = view; return true; } @OnLongClick(4) boolean clickMyInterface(MyInterface view) { last = (View) view; return true; } } @UiThreadTest @Test public void argumentCast() { class MyView extends Button implements ArgumentCast.MyInterface { MyView(Context context) { super(context); } } View view1 = new MyView(InstrumentationRegistry.getContext()); view1.setId(1); View view2 = new MyView(InstrumentationRegistry.getContext()); view2.setId(2); View view3 = new MyView(InstrumentationRegistry.getContext()); view3.setId(3); View view4 = new MyView(InstrumentationRegistry.getContext()); view4.setId(4); ViewGroup tree = new FrameLayout(InstrumentationRegistry.getContext()); tree.addView(view1); tree.addView(view2); tree.addView(view3); tree.addView(view4); ArgumentCast target = new ArgumentCast(); ButterKnife.bind(target, tree); view1.performLongClick(); assertSame(view1, target.last); view2.performLongClick(); assertSame(view2, target.last); view3.performLongClick(); assertSame(view3, target.last); view4.performLongClick(); assertSame(view4, target.last); } } ================================================ FILE: butterknife-integration-test/src/androidTest/java/com/example/butterknife/functional/OnTouchTest.java ================================================ package com.example.butterknife.functional; import android.content.Context; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.FrameLayout; import android.widget.TextView; import androidx.test.InstrumentationRegistry; import androidx.test.annotation.UiThreadTest; import butterknife.ButterKnife; import butterknife.OnTouch; import butterknife.Optional; import butterknife.Unbinder; import org.junit.Test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; @SuppressWarnings("unused") // Used reflectively / by code gen. public final class OnTouchTest { static final class Simple { boolean returnValue = true; int touches = 0; @OnTouch(1) boolean touch() { touches++; return returnValue; } } @UiThreadTest @Test public void simple() { View tree = ViewTree.create(1); View view1 = tree.findViewById(1); Simple target = new Simple(); Unbinder unbinder = ButterKnife.bind(target, tree); assertEquals(0, target.touches); assertTrue(performTouch(view1)); assertEquals(1, target.touches); target.returnValue = false; assertFalse(performTouch(view1)); assertEquals(2, target.touches); unbinder.unbind(); performTouch(view1); assertEquals(2, target.touches); } static final class Arguments { int touches = 0; @OnTouch(1) boolean touch(View v) { touches++; return true; } @OnTouch(2) boolean touch(View v, MotionEvent event) { touches++; return true; } } @UiThreadTest @Test public void arguments() { View tree = ViewTree.create(1, 2); View view1 = tree.findViewById(1); View view2 = tree.findViewById(2); Arguments target = new Arguments(); Unbinder unbinder = ButterKnife.bind(target, tree); assertEquals(0, target.touches); assertTrue(performTouch(view1)); assertEquals(1, target.touches); assertTrue(performTouch(view2)); assertEquals(2, target.touches); unbinder.unbind(); performTouch(view1); assertEquals(2, target.touches); } static final class ReturnVoid { int touches = 0; @OnTouch(1) void touch() { touches++; } } @UiThreadTest @Test public void returnVoid() { View tree = ViewTree.create(1); View view1 = tree.findViewById(1); ReturnVoid target = new ReturnVoid(); Unbinder unbinder = ButterKnife.bind(target, tree); assertEquals(0, target.touches); assertTrue(performTouch(view1)); assertEquals(1, target.touches); unbinder.unbind(); performTouch(view1); assertEquals(1, target.touches); } static final class Visibilities { int touches = 0; @OnTouch(1) public boolean publicTouch() { touches++; return true; } @OnTouch(2) boolean packageTouch() { touches++; return true; } @OnTouch(3) protected boolean protectedTouch() { touches++; return true; } } @UiThreadTest @Test public void visibilities() { View tree = ViewTree.create(1, 2, 3); View view1 = tree.findViewById(1); View view2 = tree.findViewById(2); View view3 = tree.findViewById(3); Visibilities target = new Visibilities(); ButterKnife.bind(target, tree); assertEquals(0, target.touches); performTouch(view1); assertEquals(1, target.touches); performTouch(view2); assertEquals(2, target.touches); performTouch(view3); assertEquals(3, target.touches); } static final class MultipleIds { int touches = 0; @OnTouch({1, 2}) boolean touch() { touches++; return true; } } @UiThreadTest @Test public void multipleIds() { View tree = ViewTree.create(1, 2); View view1 = tree.findViewById(1); View view2 = tree.findViewById(2); MultipleIds target = new MultipleIds(); Unbinder unbinder = ButterKnife.bind(target, tree); assertEquals(0, target.touches); performTouch(view1); assertEquals(1, target.touches); performTouch(view2); assertEquals(2, target.touches); unbinder.unbind(); performTouch(view1); performTouch(view2); assertEquals(2, target.touches); } static final class OptionalId { int touches = 0; @Optional @OnTouch(1) public boolean touch() { touches++; return true; } } @UiThreadTest @Test public void optionalIdPresent() { View tree = ViewTree.create(1); View view1 = tree.findViewById(1); OptionalId target = new OptionalId(); Unbinder unbinder = ButterKnife.bind(target, tree); assertEquals(0, target.touches); performTouch(view1); assertEquals(1, target.touches); unbinder.unbind(); performTouch(view1); assertEquals(1, target.touches); } @UiThreadTest @Test public void optionalIdAbsent() { View tree = ViewTree.create(2); View view2 = tree.findViewById(2); OptionalId target = new OptionalId(); Unbinder unbinder = ButterKnife.bind(target, tree); assertEquals(0, target.touches); performTouch(view2); assertEquals(0, target.touches); unbinder.unbind(); performTouch(view2); assertEquals(0, target.touches); } static final class ArgumentCast { interface MyInterface {} View last; @OnTouch(1) boolean touchView(View view) { last = view; return true; } @OnTouch(2) boolean touchTextView(TextView view) { last = view; return true; } @OnTouch(3) boolean touchButton(Button view) { last = view; return true; } @OnTouch(4) boolean touchMyInterface(ArgumentCast.MyInterface view) { last = (View) view; return true; } } @UiThreadTest @Test public void argumentCast() { class MyView extends Button implements ArgumentCast.MyInterface { MyView(Context context) { super(context); } } Context context = InstrumentationRegistry.getContext(); View view1 = new MyView(context); view1.setId(1); View view2 = new MyView(context); view2.setId(2); View view3 = new MyView(context); view3.setId(3); View view4 = new MyView(context); view4.setId(4); ViewGroup tree = new FrameLayout(context); tree.addView(view1); tree.addView(view2); tree.addView(view3); tree.addView(view4); ArgumentCast target = new ArgumentCast(); ButterKnife.bind(target, tree); performTouch(view1); assertSame(view1, target.last); performTouch(view2); assertSame(view2, target.last); performTouch(view3); assertSame(view3, target.last); performTouch(view4); assertSame(view4, target.last); } private static boolean performTouch(View view) { MotionEvent event = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0); return view.dispatchTouchEvent(event); } } ================================================ FILE: butterknife-integration-test/src/androidTest/java/com/example/butterknife/functional/ViewTree.java ================================================ package com.example.butterknife.functional; import android.content.Context; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; import androidx.test.InstrumentationRegistry; import java.lang.reflect.InvocationTargetException; final class ViewTree { static View create(int... ids) { return create(View.class, ids); } static View create(Class cls, int... ids) { Context context = InstrumentationRegistry.getContext(); ViewGroup group = new FrameLayout(context); for (int id : ids) { View view; try { view = cls.getConstructor(Context.class).newInstance(context); } catch (IllegalAccessException | InstantiationException | NoSuchMethodException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { Throwable cause = e.getCause(); if (cause instanceof RuntimeException) throw (RuntimeException) cause; if (cause instanceof Error) throw (Error) cause; throw new RuntimeException(cause); } view.setId(id); group.addView(view); } return group; } } ================================================ FILE: butterknife-integration-test/src/androidTest/java/com/example/butterknife/library/SimpleActivityTest.java ================================================ package com.example.butterknife.library; import androidx.test.rule.ActivityTestRule; import butterknife.ButterKnife; import butterknife.Unbinder; import com.example.butterknife.R; import org.junit.Rule; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; public final class SimpleActivityTest { @Rule public final ActivityTestRule activityRule = new ActivityTestRule<>(SimpleActivity.class); @Test public void verifyContentViewBinding() { SimpleActivity activity = activityRule.getActivity(); Unbinder unbinder = ButterKnife.bind(activity); verifySimpleActivityBound(activity); unbinder.unbind(); verifySimpleActivityUnbound(activity); } protected static void verifySimpleActivityBound(SimpleActivity activity) { assertThat(activity.title.getId()).isEqualTo(R.id.titleTv); assertThat(activity.subtitle.getId()).isEqualTo(R.id.subtitle); assertThat(activity.hello.getId()).isEqualTo(R.id.hello); assertThat(activity.listOfThings.getId()).isEqualTo(R.id.list_of_things); assertThat(activity.footer.getId()).isEqualTo(R.id.footer); } protected static void verifySimpleActivityUnbound(SimpleActivity activity) { assertThat(activity.title).isNull(); assertThat(activity.subtitle).isNull(); assertThat(activity.hello).isNull(); assertThat(activity.listOfThings).isNull(); assertThat(activity.footer).isNull(); } } ================================================ FILE: butterknife-integration-test/src/androidTest/java/com/example/butterknife/library/SimpleAdapterTest.java ================================================ package com.example.butterknife.library; import android.content.Context; import android.view.View; import androidx.test.InstrumentationRegistry; import com.example.butterknife.R; import org.junit.Test; import static com.example.butterknife.library.SimpleAdapter.ViewHolder; import static com.google.common.truth.Truth.assertThat; public class SimpleAdapterTest { @Test public void verifyViewHolderViews() { Context context = InstrumentationRegistry.getTargetContext(); View root = View.inflate(context, R.layout.simple_list_item, null); ViewHolder holder = new ViewHolder(root); assertThat(holder.word.getId()).isEqualTo(R.id.word); assertThat(holder.length.getId()).isEqualTo(R.id.length); assertThat(holder.position.getId()).isEqualTo(R.id.position); } } ================================================ FILE: butterknife-integration-test/src/androidTest/java/com/example/butterknife/unbinder/UnbinderTest.java ================================================ package com.example.butterknife.unbinder; import android.content.Context; import android.view.View; import android.widget.Button; import android.widget.FrameLayout; import androidx.test.InstrumentationRegistry; import butterknife.ButterKnife; import butterknife.Unbinder; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; public final class UnbinderTest { private final Context context = InstrumentationRegistry.getContext(); @Test public void verifyContentViewBinding() { FrameLayout frameLayout = new FrameLayout(context); Button button1 = new Button(context); button1.setId(android.R.id.button1); frameLayout.addView(button1); Button button2 = new Button(context); button2.setId(android.R.id.button2); frameLayout.addView(button2); Button button3 = new Button(context); button3.setId(android.R.id.button3); frameLayout.addView(button3); View content = new View(context); content.setId(android.R.id.content); frameLayout.addView(content); H h = new H(frameLayout); Unbinder unbinder = ButterKnife.bind(h, frameLayout); verifyHBound(h); unbinder.unbind(); verifyHUnbound(h); } private void verifyHBound(H h) { assertThat(h.button1).isNotNull(); assertThat(h.button2).isNotNull(); assertThat(h.button3).isNotNull(); } private void verifyHUnbound(H h) { assertThat(h.button1).isNull(); assertThat(h.button2).isNull(); assertThat(h.button3).isNull(); } } ================================================ FILE: butterknife-integration-test/src/androidTest/proguard.pro ================================================ -dontoptimize -dontobfuscate -dontshrink -dontnote ** -dontwarn ** ================================================ FILE: butterknife-integration-test/src/androidTest/res/color/colors.xml ================================================ ================================================ FILE: butterknife-integration-test/src/androidTest/res/drawable/circle.xml ================================================ ================================================ FILE: butterknife-integration-test/src/androidTest/res/values/values.xml ================================================ true #ffff0000 12 12.2dp 12.2 Hey One Two Three 1 2 3 ================================================ FILE: butterknife-integration-test/src/androidTestReflect/java/com/example/butterknife/functional/BindAnimFailureTest.java ================================================ package com.example.butterknife.functional; import android.view.View; import butterknife.BindAnim; import butterknife.ButterKnife; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; public final class BindAnimFailureTest { private final View tree = ViewTree.create(1); static class Target { @BindAnim(1) String actual; } @Test public void typeMustBeAnimation() { Target target = new Target(); try { ButterKnife.bind(target, tree); fail(); } catch (IllegalStateException e) { assertThat(e).hasMessageThat() .isEqualTo("@BindAnim field type must be 'Animation'. " + "(com.example.butterknife.functional.BindAnimFailureTest$Target.actual)"); } } } ================================================ FILE: butterknife-integration-test/src/androidTestReflect/java/com/example/butterknife/functional/BindArrayFailureTest.java ================================================ package com.example.butterknife.functional; import android.view.View; import butterknife.BindArray; import butterknife.ButterKnife; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; public final class BindArrayFailureTest { private final View tree = ViewTree.create(1); static class Target { @BindArray(1) String actual; } @Test public void typeMustBeSupported() { Target target = new Target(); try { ButterKnife.bind(target, tree); fail(); } catch (IllegalStateException e) { assertThat(e).hasMessageThat() .isEqualTo("@BindArray field type must be one of: " + "String[], int[], CharSequence[], android.content.res.TypedArray. " + "(com.example.butterknife.functional.BindArrayFailureTest$Target.actual)"); } } } ================================================ FILE: butterknife-integration-test/src/androidTestReflect/java/com/example/butterknife/functional/BindBitmapFailureTest.java ================================================ package com.example.butterknife.functional; import android.view.View; import butterknife.BindBitmap; import butterknife.ButterKnife; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; public final class BindBitmapFailureTest { private final View tree = ViewTree.create(1); static class Target { @BindBitmap(1) String actual; } @Test public void typeMustBeBitmap() { Target target = new Target(); try { ButterKnife.bind(target, tree); fail(); } catch (IllegalStateException e) { assertThat(e).hasMessageThat() .isEqualTo("@BindBitmap field type must be 'Bitmap'. " + "(com.example.butterknife.functional.BindBitmapFailureTest$Target.actual)"); } } } ================================================ FILE: butterknife-integration-test/src/androidTestReflect/java/com/example/butterknife/functional/BindBoolFailureTest.java ================================================ package com.example.butterknife.functional; import android.view.View; import butterknife.BindBool; import butterknife.ButterKnife; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; public final class BindBoolFailureTest { private final View tree = ViewTree.create(1); static class Target { @BindBool(1) String actual; } @Test public void typeMustBeBool() { Target target = new Target(); try { ButterKnife.bind(target, tree); fail(); } catch (IllegalStateException e) { assertThat(e).hasMessageThat() .isEqualTo("@BindBool field type must be 'boolean'. " + "(com.example.butterknife.functional.BindBoolFailureTest$Target.actual)"); } } } ================================================ FILE: butterknife-integration-test/src/androidTestReflect/java/com/example/butterknife/functional/BindColorFailureTest.java ================================================ package com.example.butterknife.functional; import android.view.View; import butterknife.BindColor; import butterknife.ButterKnife; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; public final class BindColorFailureTest { private final View tree = ViewTree.create(1); static class Target { @BindColor(1) String actual; } @Test public void typeMustBeIntOrColorStateList() { Target target = new Target(); try { ButterKnife.bind(target, tree); fail(); } catch (IllegalStateException e) { assertThat(e).hasMessageThat() .isEqualTo("@BindColor field type must be 'int' or 'ColorStateList'. " + "(com.example.butterknife.functional.BindColorFailureTest$Target.actual)"); } } } ================================================ FILE: butterknife-integration-test/src/androidTestReflect/java/com/example/butterknife/functional/BindDimenFailureTest.java ================================================ package com.example.butterknife.functional; import android.view.View; import butterknife.BindDimen; import butterknife.ButterKnife; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; public final class BindDimenFailureTest { private final View tree = ViewTree.create(1); static class Target { @BindDimen(1) String actual; } @Test public void typeMustBeIntOrFloat() { Target target = new Target(); try { ButterKnife.bind(target, tree); fail(); } catch (IllegalStateException e) { assertThat(e).hasMessageThat() .isEqualTo("@BindDimen field type must be 'int' or 'float'. " + "(com.example.butterknife.functional.BindDimenFailureTest$Target.actual)"); } } } ================================================ FILE: butterknife-integration-test/src/androidTestReflect/java/com/example/butterknife/functional/BindDrawableFailureTest.java ================================================ package com.example.butterknife.functional; import android.view.View; import butterknife.BindDrawable; import butterknife.ButterKnife; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; public final class BindDrawableFailureTest { private final View tree = ViewTree.create(1); static class Target { @BindDrawable(1) String actual; } @Test public void typeMustBeDrawable() { Target target = new Target(); try { ButterKnife.bind(target, tree); fail(); } catch (IllegalStateException e) { assertThat(e).hasMessageThat() .isEqualTo("@BindDrawable field type must be 'Drawable'. " + "(com.example.butterknife.functional.BindDrawableFailureTest$Target.actual)"); } } } ================================================ FILE: butterknife-integration-test/src/androidTestReflect/java/com/example/butterknife/functional/BindFloatFailureTest.java ================================================ package com.example.butterknife.functional; import android.view.View; import butterknife.BindFloat; import butterknife.ButterKnife; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; public final class BindFloatFailureTest { private final View tree = ViewTree.create(1); static class Target { @BindFloat(1) String actual; } @Test public void typeMustBeFloat() { Target target = new Target(); try { ButterKnife.bind(target, tree); fail(); } catch (IllegalStateException e) { assertThat(e).hasMessageThat() .isEqualTo("@BindFloat field type must be 'float'. " + "(com.example.butterknife.functional.BindFloatFailureTest$Target.actual)"); } } } ================================================ FILE: butterknife-integration-test/src/androidTestReflect/java/com/example/butterknife/functional/BindFontFailureTest.java ================================================ package com.example.butterknife.functional; import android.graphics.Typeface; import android.view.View; import androidx.test.filters.SdkSuppress; import butterknife.BindFont; import butterknife.ButterKnife; import com.example.butterknife.test.R; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; public final class BindFontFailureTest { private final View tree = ViewTree.create(1); static class TargetType { @BindFont(1) String actual; } @Test public void typeMustBeTypeface() { TargetType target = new TargetType(); try { ButterKnife.bind(target, tree); fail(); } catch (IllegalStateException e) { assertThat(e).hasMessageThat() .isEqualTo("@BindFont field type must be 'Typeface'. " + "(com.example.butterknife.functional.BindFontFailureTest$TargetType.actual)"); } } static class TargetStyle { @BindFont(value = R.font.inconsolata_regular, style = 5) Typeface actual; } @SdkSuppress(minSdkVersion = 24) // AndroidX problems on earlier versions @Test public void styleMustBeValid() { TargetStyle target = new TargetStyle(); try { ButterKnife.bind(target, tree); fail(); } catch (IllegalStateException e) { assertThat(e).hasMessageThat() .isEqualTo("@BindFont style must be NORMAL, BOLD, ITALIC, or BOLD_ITALIC. " + "(com.example.butterknife.functional.BindFontFailureTest$TargetStyle.actual)"); } } } ================================================ FILE: butterknife-integration-test/src/androidTestReflect/java/com/example/butterknife/functional/BindIntFailureTest.java ================================================ package com.example.butterknife.functional; import android.view.View; import butterknife.BindInt; import butterknife.ButterKnife; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; public final class BindIntFailureTest { private final View tree = ViewTree.create(1); static class Target { @BindInt(1) String actual; } @Test public void typeMustBeInt() { Target target = new Target(); try { ButterKnife.bind(target, tree); fail(); } catch (IllegalStateException e) { assertThat(e).hasMessageThat() .isEqualTo("@BindInt field type must be 'int'. " + "(com.example.butterknife.functional.BindIntFailureTest$Target.actual)"); } } } ================================================ FILE: butterknife-integration-test/src/androidTestReflect/java/com/example/butterknife/functional/BindStringFailureTest.java ================================================ package com.example.butterknife.functional; import android.view.View; import butterknife.BindString; import butterknife.ButterKnife; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; public final class BindStringFailureTest { private final View tree = ViewTree.create(1); static class Target { @BindString(1) boolean actual; } @Test public void typeMustBeString() { Target target = new Target(); try { ButterKnife.bind(target, tree); fail(); } catch (IllegalStateException e) { assertThat(e).hasMessageThat() .isEqualTo("@BindString field type must be 'String'. " + "(com.example.butterknife.functional.BindStringFailureTest$Target.actual)"); } } } ================================================ FILE: butterknife-integration-test/src/androidTestReflect/java/com/example/butterknife/functional/BindViewFailureTest.java ================================================ package com.example.butterknife.functional; import android.view.View; import butterknife.BindView; import butterknife.ButterKnife; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; public final class BindViewFailureTest { private final View tree = ViewTree.create(1); static class NotView { @BindView(1) String actual; } @Test public void failsIfNotView() { NotView target = new NotView(); try { ButterKnife.bind(target, tree); fail(); } catch (IllegalStateException e) { assertThat(e).hasMessageThat() .isEqualTo("@BindView fields must extend from View or be an interface. " + "(com.example.butterknife.functional.BindViewFailureTest$NotView.actual)"); } } } ================================================ FILE: butterknife-integration-test/src/androidTestReflect/java/com/example/butterknife/functional/BindViewsFailureTest.java ================================================ package com.example.butterknife.functional; import android.view.View; import butterknife.BindViews; import butterknife.ButterKnife; import java.util.Deque; import java.util.List; import org.junit.Test; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; public final class BindViewsFailureTest { private final View tree = ViewTree.create(1); static class NoIds { @BindViews({}) View[] actual; } @Test public void failsIfNoIds() { NoIds target = new NoIds(); try { ButterKnife.bind(target, tree); fail(); } catch (IllegalStateException e) { assertThat(e).hasMessageThat() .isEqualTo("@BindViews must specify at least one ID. " + "(com.example.butterknife.functional.BindViewsFailureTest$NoIds.actual)"); } } static class NoGenericType { @BindViews(1) List actual; } @Test public void failsIfNoGenericType() { NoGenericType target = new NoGenericType(); try { ButterKnife.bind(target, tree); fail(); } catch (IllegalStateException e) { assertThat(e).hasMessageThat() .isEqualTo("@BindViews List must have a generic component. " + "(com.example.butterknife.functional.BindViewsFailureTest$NoGenericType.actual)"); } } static class BadCollection { @BindViews(1) Deque actual; } @Test public void failsIfUnsupportedCollection() { BadCollection target = new BadCollection(); try { ButterKnife.bind(target, tree); fail(); } catch (IllegalStateException e) { assertThat(e).hasMessageThat() .isEqualTo("@BindViews must be a List or array. " + "(com.example.butterknife.functional.BindViewsFailureTest$BadCollection.actual)"); } } static class ListNotView { @BindViews(1) List actual; } @Test public void failsIfGenericNotView() { ListNotView target = new ListNotView(); try { ButterKnife.bind(target, tree); fail(); } catch (IllegalStateException e) { assertThat(e).hasMessageThat() .isEqualTo("@BindViews List or array type must extend from View or be an interface. " + "(com.example.butterknife.functional.BindViewsFailureTest$ListNotView.actual)"); } } static class ArrayNotView { @BindViews(1) List actual; } @Test public void failsIfArrayNotView() { ArrayNotView target = new ArrayNotView(); try { ButterKnife.bind(target, tree); fail(); } catch (IllegalStateException e) { assertThat(e).hasMessageThat() .isEqualTo("@BindViews List or array type must extend from View or be an interface. " + "(com.example.butterknife.functional.BindViewsFailureTest$ArrayNotView.actual)"); } } } ================================================ FILE: butterknife-integration-test/src/main/AndroidManifest.xml ================================================ ================================================ FILE: butterknife-integration-test/src/main/java/com/example/butterknife/SimpleApp.java ================================================ package com.example.butterknife; import android.app.Application; import butterknife.ButterKnife; public class SimpleApp extends Application { @Override public void onCreate() { super.onCreate(); ButterKnife.setDebug(BuildConfig.DEBUG); } } ================================================ FILE: butterknife-integration-test/src/main/java/com/example/butterknife/library/SimpleActivity.java ================================================ package com.example.butterknife.library; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.view.animation.AlphaAnimation; import android.widget.Button; import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; import butterknife.Action; import butterknife.BindString; import butterknife.BindView; import butterknife.BindViews; import butterknife.ButterKnife; import butterknife.OnClick; import butterknife.OnItemClick; import butterknife.OnLongClick; import butterknife.ViewCollections; import com.example.butterknife.R; import static com.example.butterknife.R.id.titleTv; import java.util.List; import static android.widget.Toast.LENGTH_SHORT; public class SimpleActivity extends Activity { private static final Action ALPHA_FADE = (view, index) -> { AlphaAnimation alphaAnimation = new AlphaAnimation(0, 1); alphaAnimation.setFillBefore(true); alphaAnimation.setDuration(500); alphaAnimation.setStartOffset(index * 100); view.startAnimation(alphaAnimation); }; @BindView(titleTv) TextView title; @BindView(R.id.subtitle) TextView subtitle; @BindView(R.id.hello) Button hello; @BindView(R.id.list_of_things) ListView listOfThings; @BindView(R.id.footer) TextView footer; @BindString(R.string.app_name) String butterKnife; @BindString(R.string.field_method) String fieldMethod; @BindString(R.string.by_jake_wharton) String byJakeWharton; @BindString(R.string.say_hello) String sayHello; @BindViews({ titleTv, R.id.subtitle, R.id.hello }) List headerViews; private SimpleAdapter adapter; @OnClick(R.id.hello) void sayHello() { Toast.makeText(this, "Hello, views!", LENGTH_SHORT).show(); ViewCollections.run(headerViews, ALPHA_FADE); } @OnLongClick(R.id.hello) boolean sayGetOffMe() { Toast.makeText(this, "Let go of me!", LENGTH_SHORT).show(); return true; } @OnItemClick(R.id.list_of_things) void onItemClick(int position) { Toast.makeText(this, "You clicked: " + adapter.getItem(position), LENGTH_SHORT).show(); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.simple_activity); ButterKnife.bind(this); // Contrived code to use the bound fields. title.setText(butterKnife); subtitle.setText(fieldMethod); footer.setText(byJakeWharton); hello.setText(sayHello); adapter = new SimpleAdapter(this); listOfThings.setAdapter(adapter); } } ================================================ FILE: butterknife-integration-test/src/main/java/com/example/butterknife/library/SimpleAdapter.java ================================================ package com.example.butterknife.library; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.TextView; import butterknife.BindView; import butterknife.ButterKnife; import com.example.butterknife.R; import java.util.Locale; public class SimpleAdapter extends BaseAdapter { private static final String[] CONTENTS = "The quick brown fox jumps over the lazy dog".split(" "); private final LayoutInflater inflater; public SimpleAdapter(Context context) { inflater = LayoutInflater.from(context); } @Override public int getCount() { return CONTENTS.length; } @Override public String getItem(int position) { return CONTENTS[position]; } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View view, ViewGroup parent) { ViewHolder holder; if (view != null) { holder = (ViewHolder) view.getTag(); } else { view = inflater.inflate(R.layout.simple_list_item, parent, false); holder = new ViewHolder(view); view.setTag(holder); } String word = getItem(position); holder.word.setText(String.format(Locale.getDefault(), "Word: %s", word)); holder.length.setText(String.format(Locale.getDefault(), "Length: %d", word.length())); holder.position.setText(String.format(Locale.getDefault(), "Position: %d", position)); // Note: don't actually do string concatenation like this in an adapter's getView. return view; } static final class ViewHolder { @BindView(R.id.word) TextView word; @BindView(R.id.length) TextView length; @BindView(R.id.position) TextView position; ViewHolder(View view) { ButterKnife.bind(this, view); } } } ================================================ FILE: butterknife-integration-test/src/main/java/com/example/butterknife/unbinder/A.java ================================================ package com.example.butterknife.unbinder; import android.view.View; import androidx.annotation.ColorInt; import butterknife.BindColor; import butterknife.ButterKnife; public class A { @BindColor(android.R.color.black) @ColorInt int blackColor; public A(View view) { ButterKnife.bind(this, view); } } ================================================ FILE: butterknife-integration-test/src/main/java/com/example/butterknife/unbinder/B.java ================================================ package com.example.butterknife.unbinder; import android.view.View; import androidx.annotation.ColorInt; import butterknife.BindColor; import butterknife.ButterKnife; public class B extends A { @BindColor(android.R.color.white) @ColorInt int whiteColor; public B(View view) { super(view); ButterKnife.bind(this, view); } } ================================================ FILE: butterknife-integration-test/src/main/java/com/example/butterknife/unbinder/C.java ================================================ package com.example.butterknife.unbinder; import android.view.View; import androidx.annotation.ColorInt; import butterknife.BindColor; import butterknife.BindView; import butterknife.ButterKnife; public class C extends B { @BindColor(android.R.color.transparent) @ColorInt int transparentColor; @BindView(android.R.id.button1) View button1; public C(View view) { super(view); ButterKnife.bind(this, view); } } ================================================ FILE: butterknife-integration-test/src/main/java/com/example/butterknife/unbinder/D.java ================================================ package com.example.butterknife.unbinder; import android.view.View; import androidx.annotation.ColorInt; import butterknife.BindColor; import butterknife.ButterKnife; public class D extends C { @BindColor(android.R.color.darker_gray) @ColorInt int grayColor; public D(View view) { super(view); ButterKnife.bind(this, view); } } ================================================ FILE: butterknife-integration-test/src/main/java/com/example/butterknife/unbinder/E.java ================================================ package com.example.butterknife.unbinder; import android.view.View; import androidx.annotation.ColorInt; import butterknife.BindColor; import butterknife.ButterKnife; public class E extends C { @BindColor(android.R.color.background_dark) @ColorInt int backgroundDarkColor; public E(View view) { super(view); ButterKnife.bind(this, view); } } ================================================ FILE: butterknife-integration-test/src/main/java/com/example/butterknife/unbinder/F.java ================================================ package com.example.butterknife.unbinder; import android.view.View; import androidx.annotation.ColorInt; import butterknife.BindColor; import butterknife.ButterKnife; public final class F extends D { @BindColor(android.R.color.background_light) @ColorInt int backgroundLightColor; public F(View view) { super(view); ButterKnife.bind(this, view); } } ================================================ FILE: butterknife-integration-test/src/main/java/com/example/butterknife/unbinder/G.java ================================================ package com.example.butterknife.unbinder; import android.view.View; import androidx.annotation.ColorInt; import butterknife.BindColor; import butterknife.BindView; import butterknife.ButterKnife; import butterknife.OnClick; import static android.R.color.darker_gray; public class G extends E { @BindColor(darker_gray) @ColorInt int grayColor; @BindView(android.R.id.button2) View button2; public G(View view) { super(view); ButterKnife.bind(this, view); } @OnClick(android.R.id.content) public void onClick() { } } ================================================ FILE: butterknife-integration-test/src/main/java/com/example/butterknife/unbinder/H.java ================================================ package com.example.butterknife.unbinder; import android.view.View; import androidx.annotation.ColorInt; import butterknife.BindColor; import butterknife.BindView; import butterknife.ButterKnife; public class H extends G { @BindColor(android.R.color.holo_green_dark) @ColorInt int holoGreenDark; @BindView(android.R.id.button3) View button3; public H(View view) { super(view); ButterKnife.bind(this, view); } } ================================================ FILE: butterknife-integration-test/src/main/proguard.pro ================================================ -dontoptimize -dontobfuscate -dontnote ** -dontwarn ** # STUFF USED BY TESTS: -keep class butterknife.internal.Utils { ; } -keep class butterknife.Unbinder { void unbind(); } -keep class com.example.butterknife.unbinder.H { (...); } -keep class androidx.core.** { ; } ================================================ FILE: butterknife-integration-test/src/main/res/layout/simple_activity.xml ================================================