├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE.txt ├── README.md ├── activity ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ └── com │ │ └── pascalwelsch │ │ └── compositeandroid │ │ └── activity │ │ ├── ActivityDelegate.java │ │ ├── ActivityPlugin.java │ │ ├── CompositeActivity.java │ │ ├── CompositeNonConfigurationInstance.java │ │ ├── ICompositeActivity.java │ │ ├── NonConfigurationInstanceWrapper.java │ │ └── SuppressedException.java │ └── test │ └── java │ └── com │ └── pascalwelsch │ └── compositeandroid │ └── activity │ ├── ActivityDelegateTest.java │ └── NonConfigurationInstance.java ├── blueprints ├── .gitignore ├── README.md ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── com │ └── pascalwelsch │ └── compositeandroid │ └── blueprints │ ├── BlueprintActivity.java │ ├── BlueprintDialogFragment.java │ └── BlueprintFragment.java ├── build.gradle ├── core ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ └── com │ │ └── pascalwelsch │ │ └── compositeandroid │ │ └── core │ │ ├── AbstractDelegate.java │ │ ├── AbstractPlugin.java │ │ ├── CallFun0.java │ │ ├── CallFun1.java │ │ ├── CallFun2.java │ │ ├── CallFun3.java │ │ ├── CallFun4.java │ │ ├── CallFun5.java │ │ ├── CallFun6.java │ │ ├── CallFun7.java │ │ ├── CallFun8.java │ │ ├── CallFun9.java │ │ ├── CallVoid0.java │ │ ├── CallVoid1.java │ │ ├── CallVoid2.java │ │ ├── CallVoid3.java │ │ ├── CallVoid4.java │ │ ├── CallVoid5.java │ │ ├── CallVoid6.java │ │ ├── CallVoid7.java │ │ ├── CallVoid8.java │ │ ├── CallVoid9.java │ │ ├── NamedSuperCall.java │ │ ├── NamedSuperCallVoid.java │ │ ├── Removable.java │ │ ├── SuperCall.java │ │ ├── SuperCallVoid.java │ │ └── SuppressedException.java │ └── test │ └── java │ └── com │ └── pascalwelsch │ └── compositeandroid │ └── core │ ├── MixinInheritance.java │ └── NormalInheritance.java ├── fragment ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ └── com │ │ └── pascalwelsch │ │ └── compositeandroid │ │ └── fragment │ │ ├── CompositeDialogFragment.java │ │ ├── CompositeFragment.java │ │ ├── DialogFragmentDelegate.java │ │ ├── DialogFragmentPlugin.java │ │ ├── FragmentDelegate.java │ │ ├── FragmentPlugin.java │ │ ├── ICompositeDialogFragment.java │ │ └── ICompositeFragment.java │ └── test │ └── java │ └── com │ └── pascalwelsch │ └── compositeandroid │ └── fragment │ └── FragmentPluginTest.java ├── generator ├── .gitignore ├── README.md ├── build.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src │ └── main │ ├── java │ └── Main.java │ └── kotlin │ ├── Generator.kt │ ├── parse │ ├── AnalyzedJavaFile.kt │ └── JavaFileParser.kt │ ├── types │ ├── Activity.kt │ └── Fragment.kt │ └── writer │ ├── CompositeWriter.kt │ ├── DelegateWriter.kt │ ├── InterfaceWriter.kt │ └── PluginWriter.kt ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── sample ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── pascalwelsch │ │ └── compositeandroid │ │ ├── ActivityTracking.java │ │ ├── ActivityTracking2.java │ │ ├── MainActivity.java │ │ ├── fragment │ │ ├── DialogTracking.java │ │ ├── FragmentTracking.java │ │ └── TestFragment.java │ │ └── performance │ │ ├── CompositePerformanceTestActivity.java │ │ ├── PerformanceTestActivity.java │ │ ├── PerformanceTestActivity1.java │ │ ├── PerformanceTestActivity2.java │ │ ├── PerformanceTestActivity3.java │ │ ├── PerformanceTestActivity4.java │ │ └── PerformanceTestActivity5.java │ └── res │ ├── layout │ ├── activity_main.xml │ └── activity_performance.xml │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ ├── mipmap-xxxhdpi │ └── ic_launcher.png │ ├── values-w820dp │ └── dimens.xml │ └── values │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | .DS_Store 3 | 4 | # Built application files 5 | *.apk 6 | *.ap_ 7 | 8 | # Files for the ART/Dalvik VM 9 | *.dex 10 | 11 | # Java class files 12 | *.class 13 | 14 | # Generated files 15 | bin/ 16 | gen/ 17 | out/ 18 | 19 | # Gradle files 20 | .gradle/ 21 | build/ 22 | 23 | # Local configuration file (sdk path, etc) 24 | local.properties 25 | 26 | # Log Files 27 | *.log 28 | 29 | # Android Studio Navigation editor temp files 30 | .navigation/ 31 | 32 | # Android Studio captures folder 33 | captures/ 34 | 35 | # Intellij 36 | *.iml 37 | **/.idea/* -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: android 2 | jdk: 3 | - oraclejdk8 4 | 5 | android: 6 | components: 7 | # Uncomment the lines below if you want to 8 | # use the latest revision of Android SDK Tools 9 | - tools 10 | - platform-tools 11 | - tools 12 | 13 | # The BuildTools version used by your project 14 | - build-tools-28.0.0 15 | 16 | # The SDK version used to compile your project 17 | - android-22 18 | - android-28 19 | 20 | # Additional components 21 | - extra-google-google_play_services 22 | - extra-google-m2repository 23 | - extra-android-m2repository 24 | - addon-google_apis-google-28 25 | 26 | # Specify at least one system image, 27 | # if you need to run emulator(s) during your tests 28 | - sys-img-armeabi-v7a-android-22 29 | 30 | before_install: 31 | - yes | sdkmanager "platforms;android-27" 32 | - yes | sdkmanager "platforms;android-28" 33 | 34 | before_script: 35 | 36 | # First assemble the project before launching the emulator 37 | - ./gradlew assemble 38 | 39 | # Emulator Management: Create, Start and Wait 40 | - echo no | android create avd --force -n test -t android-22 --abi armeabi-v7a 41 | - emulator -avd test -no-skin -no-audio -no-window & 42 | - android-wait-for-emulator 43 | - adb shell input keyevent 82 & 44 | 45 | after_success: 46 | - bash <(curl -s https://codecov.io/bash) -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 0.2.1 4 | 5 | - hotfix `OutOfMemoryException` (stackoverflow) when calling `Activity#getLastCompositeCustomNonConfigurationInstance()` (#21) #22 6 | 7 | ## 0.2.0 8 | 9 | - `FragmentPlugin` can now be added to `CompositeDialogFragment` #16 10 | - Updated to support library version `23.4.0` 11 | - Project got split up into different modules #20 12 | 13 | ```gradle 14 | dependencies { 15 | 16 | // contains CompositeActivity 17 | compile 'com.pascalwelsch.compositeandroid:activity:0.2.0' 18 | 19 | // contains CompositeFragment and CompositeDialogFragment 20 | compile 'com.pascalwelsch.compositeandroid:fragment:0.2.0' 21 | 22 | 23 | // core module (not required, only abstract classes and utils) 24 | compile 'com.pascalwelsch.compositeandroid:core:0.2.0' 25 | } 26 | ``` 27 | 28 | 29 | ## 0.1.2 30 | 31 | - Hotfix of crashes caused by type problems #11 #12 32 | 33 | ```gradle 34 | dependencies { 35 | compile 'com.pascalwelsch.compositeandroid:activity:0.1.2' 36 | } 37 | ``` 38 | 39 | 40 | ## 0.1.1 (do not use, major bugs) 41 | 42 | - Added support for DialogFragments (`CompositeDialogFragment`) #8 43 | - plugin callbacks `onAddedToDelegate`, `onRemovedFromDelegated` when original is set #9 44 | 45 | 46 | ```gradle 47 | dependencies { 48 | compile 'com.pascalwelsch.compositeandroid:activity:0.1.1' 49 | } 50 | ``` 51 | 52 | 53 | ## 0.1 54 | 55 | - Added support for Fragments (`CompositeFragment`) #4 56 | - Fixed threading problems #1 57 | 58 | ```gradle 59 | dependencies { 60 | compile 'com.pascalwelsch.compositeandroid:activity:0.1' 61 | } 62 | ``` 63 | 64 | 65 | ## 0.1-alpha (do not use, major bugs) 66 | 67 | initial release 68 | 69 | - composition for `Activity` 70 | 71 | ```gradle 72 | dependencies { 73 | compile 'com.pascalwelsch.compositeandroid:activity:0.1-alpha1' 74 | } 75 | ``` 76 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2015 Pascal Welsch 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CompositeAndroid 2 | 3 | ![License](https://img.shields.io/badge/license-Apache%202-green.svg?style=flat) 4 | 5 | *Composition over inheritance* 6 | 7 | Allows to add functionality into an Android `Activity`. Just because we all have a `BaseActivity` in our projects containing too much unused stuff. When it grows, it get unmaintainable. 8 | 9 | ## Possible Usecases 10 | 11 | - Plugin for the GooglePlayApiClient handling all the edgecases 12 | - Wrap your layout in a predefined container by overriding `setContentView()` 13 | - a Plugin showing a loading spinner 14 | - a Plugin for requesting permissions and automatically handling all response codes 15 | - gradually add libraries like [Mosby](https://github.com/sockeqwe/mosby) (without extending from a `MvpActivity`) or [Flow](https://github.com/square/flow) to your Activities when you need it 16 | - and **so much more...** 17 | 18 | ## State of the Art 19 | 20 | Given you have an `Activity` showing a list of tweets (`TweetStreamActivity`) and you want add view tracking. 21 | 22 | ### Inheritance 23 | 24 | You could do it with inheritance and use `TrackedTweetStreamActivity` from now on: 25 | 26 | ```java 27 | public class TrackedTweetStreamActivity extends TweetStreamActivity { 28 | 29 | @Override 30 | protected void onResume() { 31 | super.onResume(); 32 | Analytics.trackView("stream"); 33 | } 34 | } 35 | ``` 36 | 37 | more likely you would create a `TrackedActivity` and extend the `TweetStreamActivity` from it: 38 | 39 | ```java 40 | public abstract class TrackedActivity extends AppCompatActivity { 41 | 42 | public abstract String getTrackingName(); 43 | 44 | @Override 45 | protected void onResume() { 46 | super.onResume(); 47 | Analytics.trackView(getTrackingName()); 48 | } 49 | } 50 | ``` 51 | 52 | ```java 53 | public class TrackedTweetStreamActivity extends TrackedActivity { 54 | 55 | @Override 56 | public String getTrackingName() { 57 | return "stream"; 58 | } 59 | } 60 | ``` 61 | 62 | Both solutions work but don't scale well. You'll most likely end up with big inheritance structures: 63 | 64 | ```java 65 | class MvpActivity extends AppCompatActivity { ... } 66 | 67 | class BaseActivity extends AppCompatActivity { ... } 68 | 69 | class BaseMvpActivity extends MvpActivity { ... } 70 | 71 | class WizardUiActivity extends BaseActivity { ... } 72 | 73 | class TrackedWizardUiActivity extends WizardUiActivity { ... } 74 | 75 | class TrackedBaseActivity extends BaseActivity { ... } 76 | 77 | class TrackedMvpBaseActivity extends BaseMvpActivity { ... } 78 | ``` 79 | 80 | ### Delegation 81 | 82 | Some libraries out there provide both, a specialized `Activity` extending `AppCompatActivity` and a delegate with a documentation when to call which function of the delegate in your `Activity`. 83 | 84 | ```java 85 | public class TrackingDelegate { 86 | 87 | /** 88 | * usage: 89 | *
{@code
 90 |      *
 91 |      * @Override
 92 |      * protected void onResume() {
 93 |      *     super.onResume();
 94 |      *     mTrackingDelegate.onResume();
 95 |      * }
 96 |      * } 
97 | */ 98 | public void onResume() { 99 | Analytics.trackView(""); 100 | } 101 | 102 | } 103 | ``` 104 | 105 | ```java 106 | public class TweetStreamActivity extends AppCompatActivity { 107 | 108 | private final TrackingDelegate mTrackingDelegate = new TrackingDelegate(); 109 | 110 | @Override 111 | protected void onResume() { 112 | super.onResume(); 113 | mTrackingDelegate.onResume(); 114 | } 115 | } 116 | ``` 117 | 118 | This is an elegant solution but breaks when updating such a library and the delegate call position has changed. Or when the delegate added new callbacks which don't get automatically implemented by increasing the version number in the `build.gradle`. 119 | 120 | # Composite Android solution 121 | 122 | CompositeAndroid let's you add delegates to your Activity without adding calls to the correct location. Such delegates are called `Plugins`. A Plugin is able to inject code at every position in the Activity lifecycle. It is able to override every method. 123 | 124 | 125 | #### Get Started [![Download](https://api.bintray.com/packages/passsy/maven/CompositeActivity/images/download.svg) ](https://bintray.com/passsy/maven/CompositeActivity/_latestVersion) 126 | 127 | CompositeAndroid is available via [jcenter](http://blog.bintray.com/2015/02/09/android-studio-migration-from-maven-central-to-jcenter/) 128 | 129 | ```gradle 130 | dependencies { 131 | // it's very important to use the same version as the support library 132 | def supportLibraryVersion = "25.0.0" 133 | 134 | // contains CompositeActivity 135 | implementation "com.pascalwelsch.compositeandroid:activity:$supportLibraryVersion" 136 | 137 | // contains CompositeFragment and CompositeDialogFragment 138 | implementation "com.pascalwelsch.compositeandroid:fragment:$supportLibraryVersion" 139 | 140 | 141 | // core module (not required, only abstract classes and utils) 142 | implementation "com.pascalwelsch.compositeandroid:core:$supportLibraryVersion" 143 | } 144 | ``` 145 | 146 | #### Extend from a composite implementation 147 | 148 | Extend from one of the composite implementations when you want to add plugins. This is the only inheritance you have to make. 149 | 150 | ```diff 151 | - public class MyActivity extends AppCompatActivity { 152 | + public class MyActivity extends CompositeActivity { 153 | ``` 154 | 155 | ```diff 156 | - public class MyFragment extends Fragment { // v4 support library 157 | + public class MyFragment extends CompositeFragment { 158 | ``` 159 | 160 | 161 | #### Add a plugins 162 | 163 | Use the constructor to add plugins. Do not add plugins in `#onCreate()`. That's too late. Many `Activity` methods are called before `#onCreate()` which could be important for a plugin to work. 164 | 165 | ```java 166 | public class MainActivity extends CompositeActivity { 167 | 168 | final LoadingIndicatorPlugin loadingPlugin = new LoadingIndicatorPlugin(); 169 | 170 | public MainActivity() { 171 | addPlugin(new ViewTrackingPlugin("Main")); 172 | addPlugin(loadingPlugin); 173 | } 174 | 175 | @Override 176 | public void onCreate(Bundle savedInstanceState) { 177 | super.onCreate(savedInstanceState); 178 | // ... 179 | 180 | // example usage of the LoadingIndicatorPlugin 181 | loadingPlugin.showLoadingIndicator(); 182 | } 183 | } 184 | ``` 185 | 186 | Read more about the ordering of the Plugins [here](https://github.com/passsy/CompositeAndroid/wiki/Ordering-of-plugins-and-the-result-on-the-call-order) 187 | 188 | #### Write a plugin 189 | 190 | 191 | This is the strength of CompositeAndroid. You don't really have to learn something new. It works like you'd extend you `Activity` to add functionality. Let's change the `TrackedActivity` from above and create a `ViewTrackingPlugin`. 192 | 193 | Here the original 194 | ```java 195 | public abstract class TrackedActivity extends AppCompatActivity { 196 | 197 | public abstract String getTrackingName(); 198 | 199 | @Override 200 | protected void onResume() { 201 | super.onResume(); 202 | Analytics.trackView(getTrackingName()); 203 | } 204 | } 205 | ``` 206 | 207 | As plugin: 208 | ```java 209 | public class ViewTrackingPlugin extends ActivityPlugin { 210 | 211 | private final String mViewName; 212 | 213 | protected TrackedPlugin(final String viewName) { 214 | mViewName = viewName; 215 | } 216 | 217 | @Override 218 | public void onResume() { 219 | super.onResume(); 220 | Analytics.trackView(mViewName); 221 | } 222 | } 223 | ``` 224 | 225 | The implementation inside of `onResume()` hasn't changed! 226 | 227 | ### Plugin features 228 | 229 | Here some information about plugins. The Activity example is used but it works the same for other classes, too. 230 | 231 | - it's possible to override **every** Activity method from a `Plugin` 232 | - execute code before calling `super` executes code before `super` of Activity 233 | - explicitly not calling `super` is allowed and results in not calling super of the `Activity`. (The activity will tell if the `super` call was required) 234 | - execute code after calling `super` executes code after `super` of Activity 235 | 236 | ### restrictions 237 | 238 | Not everything works exactly like you'd use inheritance. Here is a small list of minor things you have to know: 239 | 240 | #### Important for all Plugin authors 241 | - you can't call an `Activity` method of a `Plugin` such as `onResume()` or `getResources()`. Otherwise the call order of the added plugins is not guaranteed. Instead call those methods on the real `Activity` with `getActivity.onResume()` or `getActivity.getResources()`. 242 | 243 | #### onRetainNonConfigurationInstace 244 | - `CompositeActivity#onRetainCustomNonConfigurationInstance()` is final and required for internal usage, use `CompositeActivity#onRetainCompositeCustomNonConfigurationInstance()` instead 245 | - `CompositeActivity#getLastCustomNonConfigurationInstance()` is final and required for internal usage, use `CompositeActivity#getLastCompositeCustomNonConfigurationInstance()` instead 246 | - Saving a NonConfigurationInstance inside of a `Plugin` works by overriding `onRetainNonConfigurationInstance()` and returning an instance of `CompositeNonConfigurationInstance(key, object)`. Get the data again with `getLastNonConfigurationInstance(key)` and make sure you use the correct `key`. 247 | 248 | ## Project stage 249 | 250 | `CompositeAndroid` gets used in productions without major problems. There could be more performance related improvements but it works reliably right now. 251 | 252 | Minor problems are: 253 | - Support lib updates **sometimes** require and update of `CompositeAndroid`. I didn't expect this because the API should be really stable, but it happened in the past (upgrading from `24.1.0` to `24.2.0`). That's why `CompositeAndroid` has the same version name as the support library. Yes, the support library can be used with and older `CompositeAndroid` version. But it can break, as it happened already. Then again all upgrades from `24.2.1` where 100% backwards compatible. We'll see what the future brings. 254 | - Generating a new release cannot be fully automated right now. It requires some steps in Android Studio to generate overrides and format the generated sources. 255 | - Some methods are edge cases like `getLastNonConfigurationInstance()` and `onRetainCustomNonConfigurationInstance()` did require manual written code. 256 | 257 | It was a proof of conecpt and it turned out to work great. So great I haven't touched it a lot after the initial draft. Things like the documentation are still missing. 258 | I'm still keeping this project up to date but I don't invest much time in performance improvements. I don't need it, it works at it is for me. 259 | 260 | ## Inspiration and other Android Composition Libraries 261 | 262 | - [Navi](https://github.com/trello/navi) of course, but 263 | - it doesn't support all methods (only methods without return value) 264 | - it does only support code execution before or after calling `super`, not very flexible 265 | - no plugin API 266 | - [Lightcycle](https://github.com/soundcloud/lightcycle) 267 | - supports only basic lifecycle methods. 268 | - [Decorator](https://github.com/eyeem/decorator) 269 | - works only in scope of your own project. It doesn't allow including libraries providing plugins because the is no global Activity implementation. 270 | - Every "DecoratedActivity" is generated for a specific usecase based on a blueprint you have to create every time 271 | 272 | # License 273 | 274 | ``` 275 | Copyright 2016 Pascal Welsch 276 | 277 | Licensed under the Apache License, Version 2.0 (the "License"); 278 | you may not use this file except in compliance with the License. 279 | You may obtain a copy of the License at 280 | 281 | http://www.apache.org/licenses/LICENSE-2.0 282 | 283 | Unless required by applicable law or agreed to in writing, software 284 | distributed under the License is distributed on an "AS IS" BASIS, 285 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 286 | See the License for the specific language governing permissions and 287 | limitations under the License. 288 | ``` 289 | -------------------------------------------------------------------------------- /activity/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /activity/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | jcenter() 4 | google() 5 | } 6 | dependencies { 7 | classpath 'com.novoda:bintray-release:0.8.1' 8 | } 9 | } 10 | 11 | 12 | apply plugin: 'com.android.library' 13 | apply plugin: 'com.novoda.bintray-release' 14 | 15 | android { 16 | compileSdkVersion COMPILE_SDK_VERSION 17 | 18 | defaultConfig { 19 | minSdkVersion MIN_SDK_VERSION 20 | targetSdkVersion TARGET_SDK_VERSION 21 | versionCode VERSION_CODE 22 | versionName VERSION_NAME 23 | } 24 | buildTypes { 25 | release { 26 | minifyEnabled false 27 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 28 | } 29 | } 30 | 31 | dexOptions { 32 | preDexLibraries = !ciBuild 33 | javaMaxHeapSize "2g" 34 | } 35 | 36 | lintOptions { 37 | abortOnError false 38 | } 39 | 40 | testOptions { 41 | unitTests.returnDefaultValues = true 42 | } 43 | } 44 | 45 | dependencies { 46 | compile project(':core') 47 | testCompile 'junit:junit:4.12' 48 | 49 | compile "com.android.support:appcompat-v7:$supportLibraryVersion" 50 | compile "com.android.support:support-v4:$supportLibraryVersion" 51 | 52 | testCompile 'org.mockito:mockito-core:1.10.19' 53 | testCompile 'org.assertj:assertj-core:2.4.1' 54 | } 55 | 56 | 57 | // usage: 58 | // ./gradlew clean build bintrayUpload -PbintrayUser=BINTRAY_USERNAME -PbintrayKey=BINTRAY_KEY -PdryRun=true 59 | publish { 60 | userOrg = 'passsy' 61 | groupId = 'com.pascalwelsch.compositeandroid' 62 | artifactId = 'activity' 63 | uploadName = 'CompositeActivity' 64 | publishVersion = VERSION_NAME 65 | desc = 'CompositeAndroid - Composition over inheritance for Android' 66 | website = 'https://github.com/passsy/CompositeAndroid' 67 | } -------------------------------------------------------------------------------- /activity/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /usr/local/Cellar/android-sdk/23.0.2/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /activity/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /activity/src/main/java/com/pascalwelsch/compositeandroid/activity/CompositeNonConfigurationInstance.java: -------------------------------------------------------------------------------- 1 | package com.pascalwelsch.compositeandroid.activity; 2 | 3 | public class CompositeNonConfigurationInstance { 4 | 5 | private String mId; 6 | 7 | private Object mObject; 8 | 9 | public CompositeNonConfigurationInstance(final String id, final Object object) { 10 | mId = id; 11 | mObject = object; 12 | } 13 | 14 | public String getId() { 15 | return mId; 16 | } 17 | 18 | public Object getNonConfigurationInstance() { 19 | return mObject; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /activity/src/main/java/com/pascalwelsch/compositeandroid/activity/NonConfigurationInstanceWrapper.java: -------------------------------------------------------------------------------- 1 | package com.pascalwelsch.compositeandroid.activity; 2 | 3 | import android.support.annotation.Nullable; 4 | import java.util.HashMap; 5 | 6 | class NonConfigurationInstanceWrapper { 7 | 8 | private HashMap mPluginNonConfigurationInstances = new HashMap<>(); 9 | 10 | private Object mSuperNonConfigurationInstance; 11 | 12 | public NonConfigurationInstanceWrapper(final Object superNonConfigurationInstance) { 13 | this.mSuperNonConfigurationInstance = superNonConfigurationInstance; 14 | } 15 | 16 | @Nullable 17 | public Object getPluginNonConfigurationInstance(final String key) { 18 | return mPluginNonConfigurationInstances.get(key); 19 | } 20 | 21 | public Object getSuperNonConfigurationInstance() { 22 | return mSuperNonConfigurationInstance; 23 | } 24 | 25 | public void putPluginNonConfigurationInstance(final CompositeNonConfigurationInstance nci) { 26 | mPluginNonConfigurationInstances.put(nci.getId(), nci.getNonConfigurationInstance()); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /activity/src/main/java/com/pascalwelsch/compositeandroid/activity/SuppressedException.java: -------------------------------------------------------------------------------- 1 | package com.pascalwelsch.compositeandroid.activity; 2 | 3 | public class SuppressedException extends RuntimeException { 4 | 5 | /** 6 | * Constructs a new {@code RuntimeException} with the current stack trace 7 | * and the specified cause. 8 | * 9 | * @param throwable the cause of this exception. 10 | */ 11 | public SuppressedException(final Throwable throwable) { 12 | super(throwable); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /activity/src/test/java/com/pascalwelsch/compositeandroid/activity/NonConfigurationInstance.java: -------------------------------------------------------------------------------- 1 | package com.pascalwelsch.compositeandroid.activity; 2 | 3 | import static org.assertj.core.api.Java6Assertions.*; 4 | import static org.mockito.Mockito.*; 5 | 6 | import org.junit.*; 7 | 8 | public class NonConfigurationInstance { 9 | 10 | @Test 11 | public void testMultiplePlugins() throws Exception { 12 | 13 | final ActivityPlugin a = spy(new ActivityPlugin() { 14 | @Override 15 | public CompositeNonConfigurationInstance onRetainNonConfigurationInstance() { 16 | return new CompositeNonConfigurationInstance("A", "Thing"); 17 | } 18 | }); 19 | final ActivityPlugin b = spy(new ActivityPlugin() { 20 | @Override 21 | public CompositeNonConfigurationInstance onRetainNonConfigurationInstance() { 22 | return new CompositeNonConfigurationInstance("B", "Something else"); 23 | } 24 | }); 25 | final ActivityPlugin c = spy(new ActivityPlugin()); 26 | 27 | final ICompositeActivity activity = mock(ICompositeActivity.class); 28 | final ActivityDelegate delegate = new ActivityDelegate(activity); 29 | 30 | delegate.addPlugin(a); 31 | delegate.addPlugin(b); 32 | delegate.addPlugin(c); 33 | 34 | final Object nci = delegate.onRetainNonConfigurationInstance(); 35 | assertThat(nci).isInstanceOf(NonConfigurationInstanceWrapper.class); 36 | NonConfigurationInstanceWrapper wrapper = (NonConfigurationInstanceWrapper) nci; 37 | 38 | doReturn(nci).when(activity).getLastCustomNonConfigurationInstance(); 39 | 40 | assertThat(wrapper.getPluginNonConfigurationInstance("A")) 41 | .isNotNull() 42 | .isEqualTo("Thing"); 43 | 44 | assertThat(wrapper.getPluginNonConfigurationInstance("B")) 45 | .isNotNull() 46 | .isEqualTo("Something else"); 47 | 48 | assertThat(b.getLastNonConfigurationInstance("A")) 49 | .isNotNull() 50 | .isEqualTo("Thing"); 51 | 52 | assertThat(b.getLastNonConfigurationInstance("B")) 53 | .isNotNull() 54 | .isEqualTo("Something else"); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /blueprints/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /blueprints/README.md: -------------------------------------------------------------------------------- 1 | # Blueprints 2 | 3 | Blueprints are the input files for the CompositeAndroid library generation. They extend a class and override all public and protected methods (including javadoc). -------------------------------------------------------------------------------- /blueprints/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion COMPILE_SDK_VERSION 5 | 6 | defaultConfig { 7 | minSdkVersion MIN_SDK_VERSION 8 | targetSdkVersion TARGET_SDK_VERSION 9 | versionCode VERSION_CODE 10 | versionName VERSION_NAME 11 | } 12 | 13 | dexOptions { 14 | preDexLibraries = !ciBuild 15 | javaMaxHeapSize "2g" 16 | } 17 | 18 | lintOptions { 19 | abortOnError false 20 | } 21 | 22 | } 23 | 24 | dependencies { 25 | compile "com.android.support:appcompat-v7:$supportLibraryVersion" 26 | } 27 | -------------------------------------------------------------------------------- /blueprints/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /blueprints/src/main/java/com/pascalwelsch/compositeandroid/blueprints/BlueprintDialogFragment.java: -------------------------------------------------------------------------------- 1 | package com.pascalwelsch.compositeandroid.blueprints; 2 | 3 | import android.app.Dialog; 4 | import android.content.Context; 5 | import android.content.DialogInterface; 6 | import android.os.Bundle; 7 | import android.support.annotation.NonNull; 8 | import android.support.annotation.Nullable; 9 | import android.support.v4.app.DialogFragment; 10 | import android.support.v4.app.FragmentManager; 11 | import android.support.v4.app.FragmentTransaction; 12 | import android.view.LayoutInflater; 13 | 14 | // 28.0.0 15 | @SuppressWarnings("ALL") 16 | public class BlueprintDialogFragment extends DialogFragment { 17 | 18 | @Override 19 | public void onAttach(Context context) { 20 | super.onAttach(context); 21 | } 22 | 23 | @Override 24 | public void onCreate(@Nullable Bundle savedInstanceState) { 25 | super.onCreate(savedInstanceState); 26 | } 27 | 28 | @Override 29 | public void onActivityCreated(@Nullable Bundle savedInstanceState) { 30 | super.onActivityCreated(savedInstanceState); 31 | } 32 | 33 | @Override 34 | public void onStart() { 35 | super.onStart(); 36 | } 37 | 38 | @Override 39 | public void onStop() { 40 | super.onStop(); 41 | } 42 | 43 | @Override 44 | public void onSaveInstanceState(@NonNull Bundle outState) { 45 | super.onSaveInstanceState(outState); 46 | } 47 | 48 | @Override 49 | public void onDestroyView() { 50 | super.onDestroyView(); 51 | } 52 | 53 | @Override 54 | public void onDetach() { 55 | super.onDetach(); 56 | } 57 | 58 | @Override 59 | public void dismiss() { 60 | super.dismiss(); 61 | } 62 | 63 | @Override 64 | public void dismissAllowingStateLoss() { 65 | super.dismissAllowingStateLoss(); 66 | } 67 | 68 | @Override 69 | public Dialog getDialog() { 70 | return super.getDialog(); 71 | } 72 | 73 | @Override 74 | public boolean getShowsDialog() { 75 | return super.getShowsDialog(); 76 | } 77 | 78 | @Override 79 | public void setShowsDialog(boolean showsDialog) { 80 | super.setShowsDialog(showsDialog); 81 | } 82 | 83 | @Override 84 | public int getTheme() { 85 | return super.getTheme(); 86 | } 87 | 88 | @Override 89 | public boolean isCancelable() { 90 | return super.isCancelable(); 91 | } 92 | 93 | @Override 94 | public void setCancelable(boolean cancelable) { 95 | super.setCancelable(cancelable); 96 | } 97 | 98 | @Override 99 | public void onCancel(DialogInterface dialog) { 100 | super.onCancel(dialog); 101 | } 102 | 103 | @NonNull 104 | @Override 105 | public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { 106 | return super.onCreateDialog(savedInstanceState); 107 | } 108 | 109 | @Override 110 | public void onDismiss(DialogInterface dialog) { 111 | super.onDismiss(dialog); 112 | } 113 | 114 | @NonNull 115 | @Override 116 | public LayoutInflater onGetLayoutInflater(@Nullable Bundle savedInstanceState) { 117 | return super.onGetLayoutInflater(savedInstanceState); 118 | } 119 | 120 | @Override 121 | public void setStyle(int style, int theme) { 122 | super.setStyle(style, theme); 123 | } 124 | 125 | @Override 126 | public void setupDialog(Dialog dialog, int style) { 127 | super.setupDialog(dialog, style); 128 | } 129 | 130 | @Override 131 | public void show(FragmentManager manager, String tag) { 132 | super.show(manager, tag); 133 | } 134 | 135 | @Override 136 | public int show(FragmentTransaction transaction, String tag) { 137 | return super.show(transaction, tag); 138 | } 139 | 140 | @Override 141 | public void showNow(FragmentManager manager, String tag) { 142 | super.showNow(manager, tag); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | google() 6 | jcenter() 7 | } 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.1.3' 10 | 11 | // NOTE: Do not place your application dependencies here; they belong 12 | // in the individual module build.gradle files 13 | } 14 | } 15 | 16 | allprojects { 17 | repositories { 18 | google() 19 | jcenter() 20 | } 21 | tasks.withType(Javadoc) { 22 | options.addStringOption('Xdoclint:none', '-quiet') 23 | options.addStringOption('encoding', 'UTF-8') 24 | } 25 | } 26 | 27 | // major version is the SDK version 28 | def versionMajor = 28 29 | // minor version of the support library 30 | def versionMinor = 0 31 | // patch version of the support library 32 | def versionPatch = 0 33 | 34 | // composite android build version for the given support library version. 35 | // increases when composite android receives updates but the support lib doesn't update 36 | def versionBuild = 0 37 | 38 | ext { 39 | supportLibraryVersion = "$versionMajor.$versionMinor.$versionPatch" 40 | VERSION_NAME = "$supportLibraryVersion" + (versionBuild != 0 ? "-$versionBuild" : "") 41 | VERSION_CODE = versionMajor * 10000 + versionMinor * 1000 + versionPatch * 100 + versionBuild 42 | MIN_SDK_VERSION = 14 43 | TARGET_SDK_VERSION = versionMajor 44 | COMPILE_SDK_VERSION = versionMajor 45 | 46 | ciBuild = System.getenv("TRAVIS") == "true" 47 | } 48 | 49 | task clean(type: Delete) { 50 | delete rootProject.buildDir 51 | } -------------------------------------------------------------------------------- /core/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /core/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | jcenter() 4 | google() 5 | } 6 | dependencies { 7 | classpath 'com.novoda:bintray-release:0.8.1' 8 | } 9 | } 10 | 11 | 12 | apply plugin: 'com.android.library' 13 | apply plugin: 'com.novoda.bintray-release' 14 | 15 | android { 16 | compileSdkVersion COMPILE_SDK_VERSION 17 | 18 | defaultConfig { 19 | minSdkVersion MIN_SDK_VERSION 20 | targetSdkVersion TARGET_SDK_VERSION 21 | versionCode VERSION_CODE 22 | versionName VERSION_NAME 23 | } 24 | buildTypes { 25 | release { 26 | minifyEnabled false 27 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 28 | } 29 | } 30 | 31 | dexOptions { 32 | preDexLibraries = !ciBuild 33 | javaMaxHeapSize "2g" 34 | } 35 | 36 | lintOptions { 37 | abortOnError false 38 | } 39 | 40 | testOptions { 41 | unitTests.returnDefaultValues = true 42 | } 43 | } 44 | 45 | dependencies { 46 | compile fileTree(dir: 'libs', include: ['*.jar']) 47 | testCompile 'junit:junit:4.12' 48 | compile "com.android.support:appcompat-v7:$supportLibraryVersion" 49 | compile "com.android.support:support-v4:$supportLibraryVersion" 50 | 51 | testCompile 'org.mockito:mockito-core:1.10.19' 52 | testCompile 'org.assertj:assertj-core:2.4.1' 53 | } 54 | 55 | // usage: 56 | // ./gradlew clean build bintrayUpload -PbintrayUser=BINTRAY_USERNAME -PbintrayKey=BINTRAY_KEY -PdryRun=true 57 | publish { 58 | userOrg = 'passsy' 59 | groupId = 'com.pascalwelsch.compositeandroid' 60 | artifactId = 'core' 61 | uploadName = 'CompositeActivity' 62 | publishVersion = VERSION_NAME 63 | desc = 'CompositeAndroid - Composition over inheritance for Android' 64 | website = 'https://github.com/passsy/CompositeAndroid' 65 | } -------------------------------------------------------------------------------- /core/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /usr/local/Cellar/android-sdk/24.4.1_1/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /core/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /core/src/main/java/com/pascalwelsch/compositeandroid/core/AbstractDelegate.java: -------------------------------------------------------------------------------- 1 | package com.pascalwelsch.compositeandroid.core; 2 | 3 | import java.util.List; 4 | import java.util.concurrent.CopyOnWriteArrayList; 5 | 6 | /** 7 | * @param what should be delegated 8 | * @param

plugin where delegated to 9 | */ 10 | public class AbstractDelegate { 11 | 12 | protected final T mOriginal; 13 | 14 | protected List

mPlugins = new CopyOnWriteArrayList<>(); 15 | 16 | public AbstractDelegate(final T original) { 17 | mOriginal = original; 18 | } 19 | 20 | @SuppressWarnings("unchecked") 21 | public Removable addPlugin(final P plugin) { 22 | plugin.addToDelegate(this, mOriginal); 23 | mPlugins.add(plugin); 24 | 25 | return new Removable() { 26 | @Override 27 | public void remove() { 28 | mPlugins.remove(plugin); 29 | plugin.removeFromDelegate(); 30 | } 31 | }; 32 | } 33 | 34 | public T getOriginal() { 35 | return mOriginal; 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /core/src/main/java/com/pascalwelsch/compositeandroid/core/AbstractPlugin.java: -------------------------------------------------------------------------------- 1 | package com.pascalwelsch.compositeandroid.core; 2 | 3 | import java.util.Stack; 4 | 5 | public class AbstractPlugin { 6 | 7 | final public Stack mSuperListeners = new Stack<>(); 8 | 9 | private D mDelegate; 10 | 11 | private T mOriginal; 12 | 13 | public final void addToDelegate(final D delegate, final T original) { 14 | mDelegate = delegate; 15 | mOriginal = original; 16 | onAddedToDelegate(); 17 | } 18 | 19 | public D getCompositeDelegate() { 20 | return mDelegate; 21 | } 22 | 23 | public T getOriginal() { 24 | return mOriginal; 25 | } 26 | 27 | public final void removeFromDelegate() { 28 | mDelegate = null; 29 | mOriginal = null; 30 | onRemovedFromDelegated(); 31 | } 32 | 33 | public void verifyMethodCalledFromDelegate(final String method) { 34 | if (mSuperListeners.isEmpty()) { 35 | throw new IllegalStateException("Do not call " + method 36 | + " on a ActivityPlugin directly. You have to call mDelegate." + method 37 | + " or the call order of the plugins would be mixed up."); 38 | } 39 | final String superListener = mSuperListeners.peek().getMethodName(); 40 | if (!superListener.equals(method)) { 41 | throw new IllegalStateException("You may have called " 42 | + method + " from " + superListener + " instead of calling getOriginal()." 43 | + method + ". Do not call " + method 44 | + " on a ActivityPlugin directly. You have to call mDelegate." + method 45 | + " or the call order of the plugins would be mixed up."); 46 | } 47 | } 48 | 49 | /** 50 | * callback when this plugin was added to a delegate. {@link #getOriginal()} and {@link 51 | * #getCompositeDelegate()} are now set 52 | */ 53 | protected void onAddedToDelegate() { 54 | 55 | } 56 | 57 | /** 58 | * callback when this plugin was removed from the delegate. {@link #getOriginal()} and {@link 59 | * #getCompositeDelegate()} are now {@code null} 60 | */ 61 | protected void onRemovedFromDelegated() { 62 | 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /core/src/main/java/com/pascalwelsch/compositeandroid/core/CallFun0.java: -------------------------------------------------------------------------------- 1 | package com.pascalwelsch.compositeandroid.core; 2 | 3 | public abstract class CallFun0 extends NamedSuperCall { 4 | 5 | public CallFun0(final String methodName) { 6 | super(methodName); 7 | } 8 | 9 | public abstract R call(); 10 | } 11 | -------------------------------------------------------------------------------- /core/src/main/java/com/pascalwelsch/compositeandroid/core/CallFun1.java: -------------------------------------------------------------------------------- 1 | package com.pascalwelsch.compositeandroid.core; 2 | 3 | public abstract class CallFun1 extends NamedSuperCall { 4 | 5 | public CallFun1(final String methodName) { 6 | super(methodName); 7 | } 8 | 9 | public abstract R call(final T1 p1); 10 | } 11 | -------------------------------------------------------------------------------- /core/src/main/java/com/pascalwelsch/compositeandroid/core/CallFun2.java: -------------------------------------------------------------------------------- 1 | package com.pascalwelsch.compositeandroid.core; 2 | 3 | public abstract class CallFun2 extends NamedSuperCall { 4 | 5 | public CallFun2(final String methodName) { 6 | super(methodName); 7 | } 8 | 9 | public abstract R call(final T1 p1, final T2 p2); 10 | } 11 | -------------------------------------------------------------------------------- /core/src/main/java/com/pascalwelsch/compositeandroid/core/CallFun3.java: -------------------------------------------------------------------------------- 1 | package com.pascalwelsch.compositeandroid.core; 2 | 3 | public abstract class CallFun3 extends NamedSuperCall { 4 | 5 | public CallFun3(final String methodName) { 6 | super(methodName); 7 | } 8 | 9 | public abstract R call(final T1 p1, final T2 p2, final T3 p3); 10 | } 11 | -------------------------------------------------------------------------------- /core/src/main/java/com/pascalwelsch/compositeandroid/core/CallFun4.java: -------------------------------------------------------------------------------- 1 | package com.pascalwelsch.compositeandroid.core; 2 | 3 | public abstract class CallFun4 extends NamedSuperCall { 4 | 5 | public CallFun4(final String methodName) { 6 | super(methodName); 7 | } 8 | 9 | public abstract R call(final T1 p1, final T2 p2, final T3 p3, final T4 p4); 10 | } 11 | -------------------------------------------------------------------------------- /core/src/main/java/com/pascalwelsch/compositeandroid/core/CallFun5.java: -------------------------------------------------------------------------------- 1 | package com.pascalwelsch.compositeandroid.core; 2 | 3 | public abstract class CallFun5 extends NamedSuperCall { 4 | 5 | public CallFun5(final String methodName) { 6 | super(methodName); 7 | } 8 | 9 | public abstract R call(final T1 p1, final T2 p2, final T3 p3, final T4 p4, T5 p5); 10 | } 11 | -------------------------------------------------------------------------------- /core/src/main/java/com/pascalwelsch/compositeandroid/core/CallFun6.java: -------------------------------------------------------------------------------- 1 | package com.pascalwelsch.compositeandroid.core; 2 | 3 | public abstract class CallFun6 extends NamedSuperCall { 4 | 5 | public CallFun6(final String methodName) { 6 | super(methodName); 7 | } 8 | 9 | public abstract R call(final T1 p1, final T2 p2, final T3 p3, final T4 p4, T5 p5, T6 p6); 10 | } 11 | -------------------------------------------------------------------------------- /core/src/main/java/com/pascalwelsch/compositeandroid/core/CallFun7.java: -------------------------------------------------------------------------------- 1 | package com.pascalwelsch.compositeandroid.core; 2 | 3 | public abstract class CallFun7 extends NamedSuperCall { 4 | 5 | public CallFun7(final String methodName) { 6 | super(methodName); 7 | } 8 | 9 | public abstract R call(final T1 p1, final T2 p2, final T3 p3, final T4 p4, T5 p5, T6 p6, T7 p7); 10 | } 11 | -------------------------------------------------------------------------------- /core/src/main/java/com/pascalwelsch/compositeandroid/core/CallFun8.java: -------------------------------------------------------------------------------- 1 | package com.pascalwelsch.compositeandroid.core; 2 | 3 | public abstract class CallFun8 extends NamedSuperCall { 4 | 5 | public CallFun8(final String methodName) { 6 | super(methodName); 7 | } 8 | 9 | public abstract R call(final T1 p1, final T2 p2, final T3 p3, final T4 p4, T5 p5, T6 p6, T7 p7, 10 | T8 p8); 11 | } 12 | -------------------------------------------------------------------------------- /core/src/main/java/com/pascalwelsch/compositeandroid/core/CallFun9.java: -------------------------------------------------------------------------------- 1 | package com.pascalwelsch.compositeandroid.core; 2 | 3 | public abstract class CallFun9 extends NamedSuperCall { 4 | 5 | public CallFun9(final String methodName) { 6 | super(methodName); 7 | } 8 | 9 | public abstract R call(final T1 p1, final T2 p2, final T3 p3, final T4 p4, T5 p5, T6 p6, T7 p7, 10 | T8 p8, T9 p9); 11 | } 12 | -------------------------------------------------------------------------------- /core/src/main/java/com/pascalwelsch/compositeandroid/core/CallVoid0.java: -------------------------------------------------------------------------------- 1 | package com.pascalwelsch.compositeandroid.core; 2 | 3 | public abstract class CallVoid0 extends NamedSuperCall { 4 | 5 | public CallVoid0(final String methodName) { 6 | super(methodName); 7 | } 8 | 9 | public abstract void call(); 10 | } 11 | -------------------------------------------------------------------------------- /core/src/main/java/com/pascalwelsch/compositeandroid/core/CallVoid1.java: -------------------------------------------------------------------------------- 1 | package com.pascalwelsch.compositeandroid.core; 2 | 3 | public abstract class CallVoid1 extends NamedSuperCall { 4 | 5 | public CallVoid1(final String methodName) { 6 | super(methodName); 7 | } 8 | 9 | public abstract void call(final T1 p1); 10 | } 11 | -------------------------------------------------------------------------------- /core/src/main/java/com/pascalwelsch/compositeandroid/core/CallVoid2.java: -------------------------------------------------------------------------------- 1 | package com.pascalwelsch.compositeandroid.core; 2 | 3 | public abstract class CallVoid2 extends NamedSuperCall { 4 | 5 | public CallVoid2(final String methodName) { 6 | super(methodName); 7 | } 8 | 9 | public abstract void call(final T1 p1, final T2 p2); 10 | } 11 | -------------------------------------------------------------------------------- /core/src/main/java/com/pascalwelsch/compositeandroid/core/CallVoid3.java: -------------------------------------------------------------------------------- 1 | package com.pascalwelsch.compositeandroid.core; 2 | 3 | public abstract class CallVoid3 extends NamedSuperCall { 4 | 5 | public CallVoid3(final String methodName) { 6 | super(methodName); 7 | } 8 | 9 | public abstract void call(final T1 p1, final T2 p2, final T3 p3); 10 | } 11 | -------------------------------------------------------------------------------- /core/src/main/java/com/pascalwelsch/compositeandroid/core/CallVoid4.java: -------------------------------------------------------------------------------- 1 | package com.pascalwelsch.compositeandroid.core; 2 | 3 | public abstract class CallVoid4 extends NamedSuperCall { 4 | 5 | public CallVoid4(final String methodName) { 6 | super(methodName); 7 | } 8 | 9 | public abstract void call(final T1 p1, final T2 p2, final T3 p3, final T4 p4); 10 | } 11 | -------------------------------------------------------------------------------- /core/src/main/java/com/pascalwelsch/compositeandroid/core/CallVoid5.java: -------------------------------------------------------------------------------- 1 | package com.pascalwelsch.compositeandroid.core; 2 | 3 | public abstract class CallVoid5 extends NamedSuperCall { 4 | 5 | public CallVoid5(final String methodName) { 6 | super(methodName); 7 | } 8 | 9 | public abstract void call(final T1 p1, final T2 p2, final T3 p3, final T4 p4, T5 p5); 10 | } 11 | -------------------------------------------------------------------------------- /core/src/main/java/com/pascalwelsch/compositeandroid/core/CallVoid6.java: -------------------------------------------------------------------------------- 1 | package com.pascalwelsch.compositeandroid.core; 2 | 3 | public abstract class CallVoid6 extends NamedSuperCall { 4 | 5 | public CallVoid6(final String methodName) { 6 | super(methodName); 7 | } 8 | 9 | public abstract void call(final T1 p1, final T2 p2, final T3 p3, final T4 p4, T5 p5, T6 p6); 10 | } 11 | -------------------------------------------------------------------------------- /core/src/main/java/com/pascalwelsch/compositeandroid/core/CallVoid7.java: -------------------------------------------------------------------------------- 1 | package com.pascalwelsch.compositeandroid.core; 2 | 3 | public abstract class CallVoid7 extends NamedSuperCall { 4 | 5 | public CallVoid7(final String methodName) { 6 | super(methodName); 7 | } 8 | 9 | public abstract void call(final T1 p1, final T2 p2, final T3 p3, final T4 p4, T5 p5, T6 p6, 10 | T7 p7); 11 | } 12 | -------------------------------------------------------------------------------- /core/src/main/java/com/pascalwelsch/compositeandroid/core/CallVoid8.java: -------------------------------------------------------------------------------- 1 | package com.pascalwelsch.compositeandroid.core; 2 | 3 | public abstract class CallVoid8 extends NamedSuperCall { 4 | 5 | public CallVoid8(final String methodName) { 6 | super(methodName); 7 | } 8 | 9 | public abstract void call(final T1 p1, final T2 p2, final T3 p3, final T4 p4, T5 p5, T6 p6, 10 | T7 p7, T8 p8); 11 | } 12 | -------------------------------------------------------------------------------- /core/src/main/java/com/pascalwelsch/compositeandroid/core/CallVoid9.java: -------------------------------------------------------------------------------- 1 | package com.pascalwelsch.compositeandroid.core; 2 | 3 | public abstract class CallVoid9 extends NamedSuperCall { 4 | 5 | public CallVoid9(final String methodName) { 6 | super(methodName); 7 | } 8 | 9 | public abstract void call(final T1 p1, final T2 p2, final T3 p3, final T4 p4, T5 p5, T6 p6, 10 | T7 p7, T8 p8, T9 p9); 11 | } 12 | -------------------------------------------------------------------------------- /core/src/main/java/com/pascalwelsch/compositeandroid/core/NamedSuperCall.java: -------------------------------------------------------------------------------- 1 | package com.pascalwelsch.compositeandroid.core; 2 | 3 | public abstract class NamedSuperCall { 4 | 5 | private final String mMethodName; 6 | 7 | public NamedSuperCall(final String methodName) { 8 | mMethodName = methodName; 9 | } 10 | 11 | public String getMethodName() { 12 | return mMethodName; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /core/src/main/java/com/pascalwelsch/compositeandroid/core/NamedSuperCallVoid.java: -------------------------------------------------------------------------------- 1 | package com.pascalwelsch.compositeandroid.core; 2 | 3 | public abstract class NamedSuperCallVoid implements SuperCall { 4 | 5 | private final String mMethodName; 6 | 7 | public NamedSuperCallVoid(final String methodName) { 8 | mMethodName = methodName; 9 | } 10 | 11 | public String getMethodName() { 12 | return mMethodName; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /core/src/main/java/com/pascalwelsch/compositeandroid/core/Removable.java: -------------------------------------------------------------------------------- 1 | package com.pascalwelsch.compositeandroid.core; 2 | 3 | public interface Removable { 4 | 5 | void remove(); 6 | } 7 | -------------------------------------------------------------------------------- /core/src/main/java/com/pascalwelsch/compositeandroid/core/SuperCall.java: -------------------------------------------------------------------------------- 1 | package com.pascalwelsch.compositeandroid.core; 2 | 3 | public interface SuperCall { 4 | 5 | R call(Object... args); 6 | } 7 | -------------------------------------------------------------------------------- /core/src/main/java/com/pascalwelsch/compositeandroid/core/SuperCallVoid.java: -------------------------------------------------------------------------------- 1 | package com.pascalwelsch.compositeandroid.core; 2 | 3 | public interface SuperCallVoid { 4 | 5 | void call(Object... args); 6 | } 7 | -------------------------------------------------------------------------------- /core/src/main/java/com/pascalwelsch/compositeandroid/core/SuppressedException.java: -------------------------------------------------------------------------------- 1 | package com.pascalwelsch.compositeandroid.core; 2 | 3 | public class SuppressedException extends RuntimeException { 4 | 5 | /** 6 | * Constructs a new {@code RuntimeException} with the current stack trace 7 | * and the specified cause. 8 | * 9 | * @param throwable the cause of this exception. 10 | */ 11 | public SuppressedException(final Throwable throwable) { 12 | super(throwable); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /core/src/test/java/com/pascalwelsch/compositeandroid/core/MixinInheritance.java: -------------------------------------------------------------------------------- 1 | package com.pascalwelsch.compositeandroid.core; 2 | 3 | import org.junit.Test; 4 | 5 | import java.util.ArrayList; 6 | import java.util.Arrays; 7 | import java.util.List; 8 | import java.util.ListIterator; 9 | 10 | import static org.assertj.core.api.Java6Assertions.assertThat; 11 | import static org.assertj.core.api.Java6Assertions.fail; 12 | 13 | public class MixinInheritance { 14 | 15 | 16 | public static class Person { 17 | 18 | List said = new ArrayList<>(); 19 | 20 | public void say(String s) { 21 | said.add(s); 22 | } 23 | 24 | public void sayHello() { 25 | say("Hello, I'm John"); 26 | } 27 | } 28 | 29 | public static class PersonPlugin { 30 | 31 | public PersonDelegate mDelegate; 32 | 33 | SuperCall mSuperListener; 34 | 35 | public void say(String s) { 36 | mDelegate.say(s); 37 | } 38 | 39 | public void sayHello() { 40 | if (mSuperListener != null) { 41 | mSuperListener.call(); 42 | } else { 43 | throw new IllegalStateException( 44 | "Do not call this method on a ActivityPlugin directly. You have to call `mDelegate.sayHello()` or the call order of the plugins would be mixed up."); 45 | } 46 | } 47 | } 48 | 49 | public static class PersonDelegate { 50 | 51 | public PersonPlugin mCallingPlugin; 52 | 53 | List mPlugins = new ArrayList<>(); 54 | 55 | private CompositePerson mCompositePerson; 56 | 57 | public PersonDelegate( 58 | final CompositePerson compositePerson) { 59 | 60 | mCompositePerson = compositePerson; 61 | } 62 | 63 | public void say(String s) { 64 | mCompositePerson.say(s); 65 | } 66 | 67 | public void sayHello() { 68 | callHook(new PluginMethodAction() { 69 | @Override 70 | public void call(final PersonPlugin plugin, final Object... args) { 71 | plugin.sayHello(); 72 | } 73 | }, new SuperCallVoid() { 74 | @Override 75 | public void call(final Object... args) { 76 | mCompositePerson.sayHello_super(); 77 | } 78 | }); 79 | } 80 | 81 | protected void callHook( 82 | final PluginMethodAction methodCall, 83 | final SuperCallVoid activitySuper, 84 | final Object... args) { 85 | 86 | final ArrayList plugins = new ArrayList<>(mPlugins); 87 | plugins.remove(mCallingPlugin); 88 | 89 | final ListIterator iterator = plugins.listIterator(plugins.size()); 90 | callHook(iterator, methodCall, activitySuper, args); 91 | } 92 | 93 | 94 | void callHook(final ListIterator iterator, 95 | final PluginMethodAction methodCall, 96 | final SuperCallVoid activitySuper, 97 | final Object... args) { 98 | 99 | if (iterator.hasPrevious()) { 100 | final PersonPlugin plugin = iterator.previous(); 101 | final SuperCall listener 102 | = new SuperCall() { 103 | @Override 104 | public Void call(final Object... args) { 105 | callHook(iterator, methodCall, activitySuper, args); 106 | return null; 107 | } 108 | }; 109 | plugin.mSuperListener = listener; 110 | methodCall.call(plugin, args); 111 | plugin.mSuperListener = null; 112 | } else { 113 | activitySuper.call(args); 114 | } 115 | } 116 | } 117 | 118 | public static class CompositePerson extends Person { 119 | 120 | PersonDelegate delegate = new PersonDelegate(this); 121 | 122 | public void addPlugins(PersonPlugin plugin) { 123 | delegate.mPlugins.add(plugin); 124 | plugin.mDelegate = delegate; 125 | } 126 | 127 | @Override 128 | public void sayHello() { 129 | delegate.sayHello(); 130 | } 131 | 132 | public void sayHello_super() { 133 | super.sayHello(); 134 | } 135 | } 136 | 137 | public static class Boss extends PersonPlugin { 138 | 139 | @Override 140 | public void sayHello() { 141 | super.sayHello(); 142 | say("I'm your boss"); 143 | } 144 | } 145 | 146 | public static class Paranoid extends PersonPlugin { 147 | 148 | @Override 149 | public void sayHello() { 150 | say("Is somebody listening?"); 151 | super.sayHello(); 152 | } 153 | } 154 | 155 | public static class Good extends PersonPlugin { 156 | 157 | @Override 158 | public void sayHello() { 159 | super.sayHello(); 160 | say("How are you?"); 161 | } 162 | } 163 | 164 | public static class Surprise extends PersonPlugin { 165 | 166 | @Override 167 | public void sayHello() { 168 | say("Surprise!"); 169 | super.sayHello(); 170 | } 171 | } 172 | 173 | public interface PluginMethodAction { 174 | 175 | void call(PersonPlugin plugin, Object... args); 176 | } 177 | 178 | @Test 179 | public void testCallMethodFromSuper() throws Exception { 180 | 181 | final CompositePerson bossPerson = new CompositePerson(); 182 | final Boss boss = new Boss(); 183 | bossPerson.addPlugins(boss); 184 | try { 185 | boss.sayHello(); 186 | fail("Exception excepted"); 187 | } catch (IllegalStateException e) { 188 | assertThat(e).hasMessageContaining("order"); 189 | } 190 | bossPerson.sayHello(); 191 | assertThat(bossPerson.said) 192 | .isEqualTo(Arrays.asList("Hello, I'm John", "I'm your boss")); 193 | 194 | final CompositePerson paranoidPerson = new CompositePerson(); 195 | final Paranoid paranoid = new Paranoid(); 196 | paranoidPerson.addPlugins(paranoid); 197 | try { 198 | paranoid.sayHello(); 199 | fail("Exception excepted"); 200 | } catch (IllegalStateException e) { 201 | assertThat(e).hasMessageContaining("order"); 202 | } 203 | paranoidPerson.said.clear(); 204 | paranoidPerson.sayHello(); 205 | assertThat(paranoidPerson.said) 206 | .isEqualTo(Arrays.asList("Is somebody listening?", "Hello, I'm John")); 207 | 208 | final CompositePerson paranoidBoss = new CompositePerson(); 209 | paranoidBoss.addPlugins(paranoid); 210 | paranoidBoss.addPlugins(boss); 211 | try { 212 | paranoid.sayHello(); 213 | fail("Exception excepted"); 214 | } catch (IllegalStateException e) { 215 | assertThat(e).hasMessageContaining("order"); 216 | } 217 | paranoidBoss.said.clear(); 218 | paranoidBoss.sayHello(); 219 | assertThat(paranoidBoss.said) 220 | .isEqualTo(Arrays.asList("Is somebody listening?", "Hello, I'm John", 221 | "I'm your boss")); 222 | try { 223 | boss.sayHello(); 224 | fail("Exception excepted"); 225 | } catch (IllegalStateException e) { 226 | assertThat(e).hasMessageContaining("order"); 227 | } 228 | 229 | final CompositePerson goodBoss = new CompositePerson(); 230 | goodBoss.addPlugins(boss); 231 | final Good good = new Good(); 232 | goodBoss.addPlugins(good); 233 | 234 | goodBoss.sayHello(); 235 | assertThat(goodBoss.said) 236 | .isEqualTo(Arrays.asList("Hello, I'm John", "I'm your boss", "How are you?")); 237 | try { 238 | good.sayHello(); 239 | fail("Exception excepted"); 240 | } catch (IllegalStateException e) { 241 | assertThat(e).hasMessageContaining("order"); 242 | } 243 | 244 | } 245 | 246 | @Test 247 | public void testMixinInheritance() throws Exception { 248 | 249 | final CompositePerson boss = new CompositePerson(); 250 | boss.addPlugins(new Boss()); 251 | boss.sayHello(); 252 | assertThat(boss.said) 253 | .isEqualTo(Arrays.asList("Hello, I'm John", "I'm your boss")); 254 | 255 | final CompositePerson person = new CompositePerson(); 256 | person.addPlugins(new Paranoid()); 257 | person.sayHello(); 258 | assertThat(person.said) 259 | .isEqualTo(Arrays.asList("Is somebody listening?", "Hello, I'm John")); 260 | 261 | final CompositePerson paranoidBoss = new CompositePerson(); 262 | paranoidBoss.addPlugins(new Paranoid()); 263 | paranoidBoss.addPlugins(new Boss()); 264 | paranoidBoss.sayHello(); 265 | assertThat(paranoidBoss.said) 266 | .isEqualTo(Arrays.asList("Is somebody listening?", "Hello, I'm John", 267 | "I'm your boss")); 268 | 269 | final CompositePerson goodBoss = new CompositePerson(); 270 | goodBoss.addPlugins(new Boss()); 271 | goodBoss.addPlugins(new Good()); 272 | goodBoss.sayHello(); 273 | 274 | assertThat(goodBoss.said) 275 | .isEqualTo(Arrays.asList("Hello, I'm John", "I'm your boss", "How are you?")); 276 | } 277 | 278 | @Test 279 | public void testThreePlugins() throws Exception { 280 | final CompositePerson person = new CompositePerson(); 281 | final Paranoid paranoid = new Paranoid(); 282 | 283 | person.addPlugins(new Boss()); 284 | person.addPlugins(paranoid); 285 | person.addPlugins(new Surprise()); 286 | person.sayHello(); 287 | assertThat(person.said) 288 | .isEqualTo(Arrays.asList("Surprise!", "Is somebody listening?", "Hello, I'm John", 289 | "I'm your boss")); 290 | 291 | // TODO think about how this could be solved 292 | // this is the fun part. Real inheritance would call "Surprise!" first. this happens when 293 | // code gets executed before calling super and there is nothing I can do against. 294 | // at least nothing got called twice 295 | person.said.clear(); 296 | 297 | try { 298 | paranoid.sayHello(); 299 | fail("Exception excepted"); 300 | } catch (IllegalStateException e) { 301 | assertThat(e).hasMessageContaining("order"); 302 | } 303 | } 304 | 305 | } 306 | -------------------------------------------------------------------------------- /core/src/test/java/com/pascalwelsch/compositeandroid/core/NormalInheritance.java: -------------------------------------------------------------------------------- 1 | package com.pascalwelsch.compositeandroid.core; 2 | 3 | import org.junit.Test; 4 | 5 | import java.util.ArrayList; 6 | import java.util.Arrays; 7 | import java.util.List; 8 | 9 | import static org.assertj.core.api.Java6Assertions.assertThat; 10 | 11 | public class NormalInheritance { 12 | 13 | public static class Person { 14 | 15 | List said = new ArrayList<>(); 16 | 17 | public void say(String s) { 18 | said.add(s); 19 | } 20 | 21 | public void sayHello() { 22 | say("Hello, I'm John"); 23 | } 24 | } 25 | 26 | public static class Boss extends Person { 27 | 28 | public final void callSayHello() { 29 | sayHello(); 30 | } 31 | 32 | @Override 33 | public void sayHello() { 34 | super.sayHello(); 35 | say("I'm your boss"); 36 | } 37 | } 38 | 39 | public static class ParanoidPerson extends Person { 40 | 41 | public final void callSayHello() { 42 | sayHello(); 43 | } 44 | 45 | @Override 46 | public void sayHello() { 47 | say("Is somebody listening?"); 48 | super.sayHello(); 49 | } 50 | } 51 | 52 | public static class ParanoidBoss extends ParanoidPerson { 53 | 54 | @Override 55 | public void sayHello() { 56 | super.sayHello(); 57 | say("I'm your boss"); 58 | } 59 | } 60 | 61 | public static class GoodBoss extends Boss { 62 | 63 | @Override 64 | public void sayHello() { 65 | super.sayHello(); 66 | say("How are you?"); 67 | } 68 | } 69 | 70 | @Test 71 | public void testCallMethodFromSuper() throws Exception { 72 | 73 | final Boss boss = new Boss(); 74 | boss.callSayHello(); 75 | assertThat(boss.said) 76 | .isEqualTo(Arrays.asList("Hello, I'm John", "I'm your boss")); 77 | 78 | final ParanoidPerson person = new ParanoidPerson(); 79 | person.callSayHello(); 80 | assertThat(person.said) 81 | .isEqualTo(Arrays.asList("Is somebody listening?", "Hello, I'm John")); 82 | 83 | final ParanoidBoss paranoidBoss = new ParanoidBoss(); 84 | paranoidBoss.callSayHello(); 85 | assertThat(paranoidBoss.said) 86 | .isEqualTo(Arrays.asList("Is somebody listening?", "Hello, I'm John", 87 | "I'm your boss")); 88 | 89 | final GoodBoss goodBoss = new GoodBoss(); 90 | goodBoss.callSayHello(); 91 | 92 | assertThat(goodBoss.said) 93 | .isEqualTo(Arrays.asList("Hello, I'm John", "I'm your boss", "How are you?")); 94 | } 95 | 96 | @Test 97 | public void testNormalInheritance() throws Exception { 98 | 99 | final Boss boss = new Boss(); 100 | boss.sayHello(); 101 | assertThat(boss.said) 102 | .isEqualTo(Arrays.asList("Hello, I'm John", "I'm your boss")); 103 | 104 | final ParanoidPerson person = new ParanoidPerson(); 105 | person.sayHello(); 106 | assertThat(person.said) 107 | .isEqualTo(Arrays.asList("Is somebody listening?", "Hello, I'm John")); 108 | 109 | final ParanoidBoss paranoidBoss = new ParanoidBoss(); 110 | paranoidBoss.sayHello(); 111 | assertThat(paranoidBoss.said) 112 | .isEqualTo(Arrays.asList("Is somebody listening?", "Hello, I'm John", 113 | "I'm your boss")); 114 | 115 | final GoodBoss goodBoss = new GoodBoss(); 116 | goodBoss.sayHello(); 117 | 118 | assertThat(goodBoss.said) 119 | .isEqualTo(Arrays.asList("Hello, I'm John", "I'm your boss", "How are you?")); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /fragment/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /fragment/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | jcenter() 4 | google() 5 | } 6 | dependencies { 7 | classpath 'com.novoda:bintray-release:0.8.1' 8 | } 9 | } 10 | 11 | 12 | apply plugin: 'com.android.library' 13 | apply plugin: 'com.novoda.bintray-release' 14 | 15 | android { 16 | compileSdkVersion COMPILE_SDK_VERSION 17 | 18 | defaultConfig { 19 | minSdkVersion MIN_SDK_VERSION 20 | targetSdkVersion TARGET_SDK_VERSION 21 | versionCode VERSION_CODE 22 | versionName VERSION_NAME 23 | } 24 | buildTypes { 25 | release { 26 | minifyEnabled false 27 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 28 | } 29 | } 30 | 31 | dexOptions { 32 | preDexLibraries = !ciBuild 33 | javaMaxHeapSize "2g" 34 | } 35 | 36 | lintOptions { 37 | abortOnError false 38 | } 39 | 40 | testOptions { 41 | unitTests.returnDefaultValues = true 42 | } 43 | } 44 | 45 | dependencies { 46 | compile project(':core') 47 | testCompile 'junit:junit:4.12' 48 | compile "com.android.support:support-fragment:$supportLibraryVersion" 49 | 50 | testCompile 'org.mockito:mockito-core:1.10.19' 51 | testCompile 'org.assertj:assertj-core:2.4.1' 52 | } 53 | 54 | // usage: 55 | // ./gradlew clean build bintrayUpload -PbintrayUser=BINTRAY_USERNAME -PbintrayKey=BINTRAY_KEY -PdryRun=true 56 | publish { 57 | userOrg = 'passsy' 58 | groupId = 'com.pascalwelsch.compositeandroid' 59 | artifactId = 'fragment' 60 | uploadName = 'CompositeActivity' 61 | publishVersion = VERSION_NAME 62 | desc = 'CompositeAndroid - Composition over inheritance for Android' 63 | website = 'https://github.com/passsy/CompositeAndroid' 64 | } -------------------------------------------------------------------------------- /fragment/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /usr/local/Cellar/android-sdk/24.4.1_1/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /fragment/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /fragment/src/main/java/com/pascalwelsch/compositeandroid/fragment/DialogFragmentPlugin.java: -------------------------------------------------------------------------------- 1 | package com.pascalwelsch.compositeandroid.fragment; 2 | 3 | import android.app.Dialog; 4 | import android.content.Context; 5 | import android.content.DialogInterface; 6 | import android.os.Bundle; 7 | import android.support.annotation.NonNull; 8 | import android.support.annotation.Nullable; 9 | import android.support.v4.app.DialogFragment; 10 | import android.support.v4.app.FragmentManager; 11 | import android.support.v4.app.FragmentTransaction; 12 | import android.view.LayoutInflater; 13 | import com.pascalwelsch.compositeandroid.core.CallFun0; 14 | import com.pascalwelsch.compositeandroid.core.CallFun1; 15 | import com.pascalwelsch.compositeandroid.core.CallFun2; 16 | import com.pascalwelsch.compositeandroid.core.CallVoid0; 17 | import com.pascalwelsch.compositeandroid.core.CallVoid1; 18 | import com.pascalwelsch.compositeandroid.core.CallVoid2; 19 | 20 | 21 | /** 22 | * This code was auto-generated by the CompositeAndroid 23 | * generator 24 | * 25 | * @author Pascal Welsch 26 | */ 27 | @SuppressWarnings("unused") 28 | public class DialogFragmentPlugin extends FragmentPlugin { 29 | 30 | 31 | public void onAttach(Context context) { 32 | verifyMethodCalledFromDelegate("onAttach(Context)"); 33 | ((CallVoid1) mSuperListeners.pop()).call(context); 34 | } 35 | 36 | void onAttach(final CallVoid1 superCall, Context context) { 37 | synchronized (mSuperListeners) { 38 | mSuperListeners.push(superCall); 39 | onAttach(context); 40 | } 41 | } 42 | 43 | 44 | public void onCreate(@Nullable Bundle savedInstanceState) { 45 | verifyMethodCalledFromDelegate("onCreate(Bundle)"); 46 | ((CallVoid1) mSuperListeners.pop()).call(savedInstanceState); 47 | } 48 | 49 | void onCreate(final CallVoid1 superCall, @Nullable Bundle savedInstanceState) { 50 | synchronized (mSuperListeners) { 51 | mSuperListeners.push(superCall); 52 | onCreate(savedInstanceState); 53 | } 54 | } 55 | 56 | 57 | public void onActivityCreated(@Nullable Bundle savedInstanceState) { 58 | verifyMethodCalledFromDelegate("onActivityCreated(Bundle)"); 59 | ((CallVoid1) mSuperListeners.pop()).call(savedInstanceState); 60 | } 61 | 62 | void onActivityCreated(final CallVoid1 superCall, @Nullable Bundle savedInstanceState) { 63 | synchronized (mSuperListeners) { 64 | mSuperListeners.push(superCall); 65 | onActivityCreated(savedInstanceState); 66 | } 67 | } 68 | 69 | 70 | public void onStart() { 71 | verifyMethodCalledFromDelegate("onStart()"); 72 | ((CallVoid0) mSuperListeners.pop()).call(); 73 | } 74 | 75 | void onStart(final CallVoid0 superCall) { 76 | synchronized (mSuperListeners) { 77 | mSuperListeners.push(superCall); 78 | onStart(); 79 | } 80 | } 81 | 82 | 83 | public void onStop() { 84 | verifyMethodCalledFromDelegate("onStop()"); 85 | ((CallVoid0) mSuperListeners.pop()).call(); 86 | } 87 | 88 | void onStop(final CallVoid0 superCall) { 89 | synchronized (mSuperListeners) { 90 | mSuperListeners.push(superCall); 91 | onStop(); 92 | } 93 | } 94 | 95 | 96 | public void onSaveInstanceState(@NonNull Bundle outState) { 97 | verifyMethodCalledFromDelegate("onSaveInstanceState(Bundle)"); 98 | ((CallVoid1) mSuperListeners.pop()).call(outState); 99 | } 100 | 101 | void onSaveInstanceState(final CallVoid1 superCall, @NonNull Bundle outState) { 102 | synchronized (mSuperListeners) { 103 | mSuperListeners.push(superCall); 104 | onSaveInstanceState(outState); 105 | } 106 | } 107 | 108 | 109 | public void onDestroyView() { 110 | verifyMethodCalledFromDelegate("onDestroyView()"); 111 | ((CallVoid0) mSuperListeners.pop()).call(); 112 | } 113 | 114 | void onDestroyView(final CallVoid0 superCall) { 115 | synchronized (mSuperListeners) { 116 | mSuperListeners.push(superCall); 117 | onDestroyView(); 118 | } 119 | } 120 | 121 | 122 | public void onDetach() { 123 | verifyMethodCalledFromDelegate("onDetach()"); 124 | ((CallVoid0) mSuperListeners.pop()).call(); 125 | } 126 | 127 | void onDetach(final CallVoid0 superCall) { 128 | synchronized (mSuperListeners) { 129 | mSuperListeners.push(superCall); 130 | onDetach(); 131 | } 132 | } 133 | 134 | 135 | public void dismiss() { 136 | verifyMethodCalledFromDelegate("dismiss()"); 137 | ((CallVoid0) mSuperListeners.pop()).call(); 138 | } 139 | 140 | public void dismissAllowingStateLoss() { 141 | verifyMethodCalledFromDelegate("dismissAllowingStateLoss()"); 142 | ((CallVoid0) mSuperListeners.pop()).call(); 143 | } 144 | 145 | public Dialog getDialog() { 146 | verifyMethodCalledFromDelegate("getDialog()"); 147 | return ((CallFun0

) mSuperListeners.pop()).call(); 148 | } 149 | 150 | public DialogFragment getDialogFragment() { 151 | return (DialogFragment) getOriginal(); 152 | } 153 | 154 | public boolean getShowsDialog() { 155 | verifyMethodCalledFromDelegate("getShowsDialog()"); 156 | return ((CallFun0) mSuperListeners.pop()).call(); 157 | } 158 | 159 | public void setShowsDialog(boolean showsDialog) { 160 | verifyMethodCalledFromDelegate("setShowsDialog(Boolean)"); 161 | ((CallVoid1) mSuperListeners.pop()).call(showsDialog); 162 | } 163 | 164 | public int getTheme() { 165 | verifyMethodCalledFromDelegate("getTheme()"); 166 | return ((CallFun0) mSuperListeners.pop()).call(); 167 | } 168 | 169 | public boolean isCancelable() { 170 | verifyMethodCalledFromDelegate("isCancelable()"); 171 | return ((CallFun0) mSuperListeners.pop()).call(); 172 | } 173 | 174 | public void setCancelable(boolean cancelable) { 175 | verifyMethodCalledFromDelegate("setCancelable(Boolean)"); 176 | ((CallVoid1) mSuperListeners.pop()).call(cancelable); 177 | } 178 | 179 | public void onCancel(DialogInterface dialog) { 180 | verifyMethodCalledFromDelegate("onCancel(DialogInterface)"); 181 | ((CallVoid1) mSuperListeners.pop()).call(dialog); 182 | } 183 | 184 | public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { 185 | verifyMethodCalledFromDelegate("onCreateDialog(Bundle)"); 186 | return ((CallFun1) mSuperListeners.pop()).call(savedInstanceState); 187 | } 188 | 189 | public void onDismiss(DialogInterface dialog) { 190 | verifyMethodCalledFromDelegate("onDismiss(DialogInterface)"); 191 | ((CallVoid1) mSuperListeners.pop()).call(dialog); 192 | } 193 | 194 | public LayoutInflater onGetLayoutInflater(@Nullable Bundle savedInstanceState) { 195 | verifyMethodCalledFromDelegate("onGetLayoutInflater(Bundle)"); 196 | return ((CallFun1) mSuperListeners.pop()).call(savedInstanceState); 197 | } 198 | 199 | public void setStyle(int style, int theme) { 200 | verifyMethodCalledFromDelegate("setStyle(Integer, Integer)"); 201 | ((CallVoid2) mSuperListeners.pop()).call(style, theme); 202 | } 203 | 204 | public void setupDialog(Dialog dialog, int style) { 205 | verifyMethodCalledFromDelegate("setupDialog(Dialog, Integer)"); 206 | ((CallVoid2) mSuperListeners.pop()).call(dialog, style); 207 | } 208 | 209 | public void show(FragmentManager manager, String tag) { 210 | verifyMethodCalledFromDelegate("show(FragmentManager, String)"); 211 | ((CallVoid2) mSuperListeners.pop()).call(manager, tag); 212 | } 213 | 214 | public int show(FragmentTransaction transaction, String tag) { 215 | verifyMethodCalledFromDelegate("show(FragmentTransaction, String)"); 216 | return ((CallFun2) mSuperListeners.pop()).call(transaction, tag); 217 | } 218 | 219 | public void showNow(FragmentManager manager, String tag) { 220 | verifyMethodCalledFromDelegate("showNow(FragmentManager, String)"); 221 | ((CallVoid2) mSuperListeners.pop()).call(manager, tag); 222 | } 223 | 224 | void dismiss(final CallVoid0 superCall) { 225 | synchronized (mSuperListeners) { 226 | mSuperListeners.push(superCall); 227 | dismiss(); 228 | } 229 | } 230 | 231 | void dismissAllowingStateLoss(final CallVoid0 superCall) { 232 | synchronized (mSuperListeners) { 233 | mSuperListeners.push(superCall); 234 | dismissAllowingStateLoss(); 235 | } 236 | } 237 | 238 | Dialog getDialog(final CallFun0 superCall) { 239 | synchronized (mSuperListeners) { 240 | mSuperListeners.push(superCall); 241 | return getDialog(); 242 | } 243 | } 244 | 245 | boolean getShowsDialog(final CallFun0 superCall) { 246 | synchronized (mSuperListeners) { 247 | mSuperListeners.push(superCall); 248 | return getShowsDialog(); 249 | } 250 | } 251 | 252 | int getTheme(final CallFun0 superCall) { 253 | synchronized (mSuperListeners) { 254 | mSuperListeners.push(superCall); 255 | return getTheme(); 256 | } 257 | } 258 | 259 | boolean isCancelable(final CallFun0 superCall) { 260 | synchronized (mSuperListeners) { 261 | mSuperListeners.push(superCall); 262 | return isCancelable(); 263 | } 264 | } 265 | 266 | void onCancel(final CallVoid1 superCall, DialogInterface dialog) { 267 | synchronized (mSuperListeners) { 268 | mSuperListeners.push(superCall); 269 | onCancel(dialog); 270 | } 271 | } 272 | 273 | Dialog onCreateDialog(final CallFun1 superCall, @Nullable Bundle savedInstanceState) { 274 | synchronized (mSuperListeners) { 275 | mSuperListeners.push(superCall); 276 | return onCreateDialog(savedInstanceState); 277 | } 278 | } 279 | 280 | void onDismiss(final CallVoid1 superCall, DialogInterface dialog) { 281 | synchronized (mSuperListeners) { 282 | mSuperListeners.push(superCall); 283 | onDismiss(dialog); 284 | } 285 | } 286 | 287 | LayoutInflater onGetLayoutInflater(final CallFun1 superCall, 288 | @Nullable Bundle savedInstanceState) { 289 | synchronized (mSuperListeners) { 290 | mSuperListeners.push(superCall); 291 | return onGetLayoutInflater(savedInstanceState); 292 | } 293 | } 294 | 295 | void setCancelable(final CallVoid1 superCall, boolean cancelable) { 296 | synchronized (mSuperListeners) { 297 | mSuperListeners.push(superCall); 298 | setCancelable(cancelable); 299 | } 300 | } 301 | 302 | void setShowsDialog(final CallVoid1 superCall, boolean showsDialog) { 303 | synchronized (mSuperListeners) { 304 | mSuperListeners.push(superCall); 305 | setShowsDialog(showsDialog); 306 | } 307 | } 308 | 309 | void setStyle(final CallVoid2 superCall, int style, int theme) { 310 | synchronized (mSuperListeners) { 311 | mSuperListeners.push(superCall); 312 | setStyle(style, theme); 313 | } 314 | } 315 | 316 | void setupDialog(final CallVoid2 superCall, Dialog dialog, int style) { 317 | synchronized (mSuperListeners) { 318 | mSuperListeners.push(superCall); 319 | setupDialog(dialog, style); 320 | } 321 | } 322 | 323 | void show(final CallVoid2 superCall, FragmentManager manager, String tag) { 324 | synchronized (mSuperListeners) { 325 | mSuperListeners.push(superCall); 326 | show(manager, tag); 327 | } 328 | } 329 | 330 | int show(final CallFun2 superCall, FragmentTransaction transaction, 331 | String tag) { 332 | synchronized (mSuperListeners) { 333 | mSuperListeners.push(superCall); 334 | return show(transaction, tag); 335 | } 336 | } 337 | 338 | void showNow(final CallVoid2 superCall, FragmentManager manager, String tag) { 339 | synchronized (mSuperListeners) { 340 | mSuperListeners.push(superCall); 341 | showNow(manager, tag); 342 | } 343 | } 344 | 345 | 346 | } -------------------------------------------------------------------------------- /fragment/src/main/java/com/pascalwelsch/compositeandroid/fragment/ICompositeDialogFragment.java: -------------------------------------------------------------------------------- 1 | package com.pascalwelsch.compositeandroid.fragment; 2 | 3 | import android.app.Dialog; 4 | import android.content.Context; 5 | import android.content.DialogInterface; 6 | import android.os.Bundle; 7 | import android.support.annotation.NonNull; 8 | import android.support.annotation.Nullable; 9 | import android.support.v4.app.FragmentManager; 10 | import android.support.v4.app.FragmentTransaction; 11 | import android.view.LayoutInflater; 12 | 13 | 14 | /** 15 | * This code was auto-generated by the CompositeAndroid 16 | * generator 17 | * 18 | * @author Pascal Welsch 19 | */ 20 | public interface ICompositeDialogFragment extends ICompositeFragment { 21 | 22 | 23 | void onAttach(Context context); 24 | 25 | void onCreate(@Nullable Bundle savedInstanceState); 26 | 27 | void onActivityCreated(@Nullable Bundle savedInstanceState); 28 | 29 | void onStart(); 30 | 31 | void onStop(); 32 | 33 | void onSaveInstanceState(@NonNull Bundle outState); 34 | 35 | void onDestroyView(); 36 | 37 | void onDetach(); 38 | 39 | void dismiss(); 40 | 41 | void dismissAllowingStateLoss(); 42 | 43 | Dialog getDialog(); 44 | 45 | boolean getShowsDialog(); 46 | 47 | void setShowsDialog(boolean showsDialog); 48 | 49 | int getTheme(); 50 | 51 | boolean isCancelable(); 52 | 53 | void setCancelable(boolean cancelable); 54 | 55 | void onCancel(DialogInterface dialog); 56 | 57 | Dialog onCreateDialog(@Nullable Bundle savedInstanceState); 58 | 59 | void onDismiss(DialogInterface dialog); 60 | 61 | LayoutInflater onGetLayoutInflater(@Nullable Bundle savedInstanceState); 62 | 63 | void setStyle(int style, int theme); 64 | 65 | void setupDialog(Dialog dialog, int style); 66 | 67 | void show(FragmentManager manager, String tag); 68 | 69 | int show(FragmentTransaction transaction, String tag); 70 | 71 | void showNow(FragmentManager manager, String tag); 72 | 73 | void super_dismiss(); 74 | 75 | void super_dismissAllowingStateLoss(); 76 | 77 | Dialog super_getDialog(); 78 | 79 | boolean super_getShowsDialog(); 80 | 81 | int super_getTheme(); 82 | 83 | boolean super_isCancelable(); 84 | 85 | void super_onActivityCreated(@Nullable Bundle savedInstanceState); 86 | 87 | void super_onAttach(Context context); 88 | 89 | void super_onCancel(DialogInterface dialog); 90 | 91 | void super_onCreate(@Nullable Bundle savedInstanceState); 92 | 93 | Dialog super_onCreateDialog(@Nullable Bundle savedInstanceState); 94 | 95 | void super_onDestroyView(); 96 | 97 | void super_onDetach(); 98 | 99 | void super_onDismiss(DialogInterface dialog); 100 | 101 | LayoutInflater super_onGetLayoutInflater(@Nullable Bundle savedInstanceState); 102 | 103 | void super_onSaveInstanceState(@NonNull Bundle outState); 104 | 105 | void super_onStart(); 106 | 107 | void super_onStop(); 108 | 109 | void super_setCancelable(boolean cancelable); 110 | 111 | void super_setShowsDialog(boolean showsDialog); 112 | 113 | void super_setStyle(int style, int theme); 114 | 115 | void super_setupDialog(Dialog dialog, int style); 116 | 117 | void super_show(FragmentManager manager, String tag); 118 | 119 | int super_show(FragmentTransaction transaction, String tag); 120 | 121 | void super_showNow(FragmentManager manager, String tag); 122 | } 123 | -------------------------------------------------------------------------------- /fragment/src/main/java/com/pascalwelsch/compositeandroid/fragment/ICompositeFragment.java: -------------------------------------------------------------------------------- 1 | package com.pascalwelsch.compositeandroid.fragment; 2 | 3 | import android.animation.Animator; 4 | import android.app.Activity; 5 | import android.arch.lifecycle.LiveData; 6 | import android.arch.lifecycle.ViewModelStore; 7 | import android.content.Context; 8 | import android.content.Intent; 9 | import android.content.IntentSender; 10 | import android.content.IntentSender.SendIntentException; 11 | import android.content.res.Configuration; 12 | import android.os.Bundle; 13 | import android.support.annotation.NonNull; 14 | import android.support.annotation.Nullable; 15 | import android.support.v4.app.Fragment; 16 | import android.support.v4.app.LoaderManager; 17 | import android.support.v4.app.SharedElementCallback; 18 | import android.util.AttributeSet; 19 | import android.view.ContextMenu; 20 | import android.view.ContextMenu.ContextMenuInfo; 21 | import android.view.LayoutInflater; 22 | import android.view.Menu; 23 | import android.view.MenuInflater; 24 | import android.view.MenuItem; 25 | import android.view.View; 26 | import android.view.ViewGroup; 27 | import android.view.animation.Animation; 28 | import java.io.FileDescriptor; 29 | import java.io.PrintWriter; 30 | 31 | /** 32 | * This code was auto-generated by the CompositeAndroid 33 | * generator 34 | * 35 | * @author Pascal Welsch 36 | */ 37 | public interface ICompositeFragment { 38 | 39 | 40 | void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args); 41 | 42 | boolean getAllowEnterTransitionOverlap(); 43 | 44 | void setAllowEnterTransitionOverlap(boolean allow); 45 | 46 | boolean getAllowReturnTransitionOverlap(); 47 | 48 | void setAllowReturnTransitionOverlap(boolean allow); 49 | 50 | Context getContext(); 51 | 52 | Object getEnterTransition(); 53 | 54 | void setEnterTransition(@Nullable Object transition); 55 | 56 | Object getExitTransition(); 57 | 58 | void setExitTransition(@Nullable Object transition); 59 | 60 | LayoutInflater getLayoutInflater(@Nullable Bundle savedFragmentState); 61 | 62 | android.arch.lifecycle.Lifecycle getLifecycle(); 63 | 64 | LoaderManager getLoaderManager(); 65 | 66 | Object getReenterTransition(); 67 | 68 | void setReenterTransition(@Nullable Object transition); 69 | 70 | Object getReturnTransition(); 71 | 72 | void setReturnTransition(@Nullable Object transition); 73 | 74 | Object getSharedElementEnterTransition(); 75 | 76 | void setSharedElementEnterTransition(@Nullable Object transition); 77 | 78 | Object getSharedElementReturnTransition(); 79 | 80 | void setSharedElementReturnTransition(@Nullable Object transition); 81 | 82 | boolean getUserVisibleHint(); 83 | 84 | void setUserVisibleHint(boolean isVisibleToUser); 85 | 86 | View getView(); 87 | 88 | android.arch.lifecycle.LifecycleOwner getViewLifecycleOwner(); 89 | 90 | LiveData getViewLifecycleOwnerLiveData(); 91 | 92 | ViewModelStore getViewModelStore(); 93 | 94 | void onActivityCreated(@Nullable Bundle savedInstanceState); 95 | 96 | void onActivityResult(int requestCode, int resultCode, Intent data); 97 | 98 | void onAttach(Context context); 99 | 100 | void onAttach(Activity activity); 101 | 102 | void onAttachFragment(Fragment childFragment); 103 | 104 | void onConfigurationChanged(Configuration newConfig); 105 | 106 | boolean onContextItemSelected(MenuItem item); 107 | 108 | void onCreate(@Nullable Bundle savedInstanceState); 109 | 110 | Animation onCreateAnimation(int transit, boolean enter, int nextAnim); 111 | 112 | Animator onCreateAnimator(int transit, boolean enter, int nextAnim); 113 | 114 | void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo); 115 | 116 | void onCreateOptionsMenu(Menu menu, MenuInflater inflater); 117 | 118 | View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, 119 | @Nullable Bundle savedInstanceState); 120 | 121 | void onDestroy(); 122 | 123 | void onDestroyOptionsMenu(); 124 | 125 | void onDestroyView(); 126 | 127 | void onDetach(); 128 | 129 | LayoutInflater onGetLayoutInflater(@Nullable Bundle savedInstanceState); 130 | 131 | void onHiddenChanged(boolean hidden); 132 | 133 | void onInflate(Context context, AttributeSet attrs, Bundle savedInstanceState); 134 | 135 | void onInflate(Activity activity, AttributeSet attrs, Bundle savedInstanceState); 136 | 137 | void onLowMemory(); 138 | 139 | void onMultiWindowModeChanged(boolean isInMultiWindowMode); 140 | 141 | boolean onOptionsItemSelected(MenuItem item); 142 | 143 | void onOptionsMenuClosed(Menu menu); 144 | 145 | void onPause(); 146 | 147 | void onPictureInPictureModeChanged(boolean isInPictureInPictureMode); 148 | 149 | void onPrepareOptionsMenu(Menu menu); 150 | 151 | void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults); 152 | 153 | void onResume(); 154 | 155 | void onSaveInstanceState(@NonNull Bundle outState); 156 | 157 | void onStart(); 158 | 159 | void onStop(); 160 | 161 | void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState); 162 | 163 | void onViewStateRestored(@Nullable Bundle savedInstanceState); 164 | 165 | void postponeEnterTransition(); 166 | 167 | void registerForContextMenu(View view); 168 | 169 | void setArguments(@Nullable Bundle args); 170 | 171 | void setEnterSharedElementCallback(SharedElementCallback callback); 172 | 173 | void setExitSharedElementCallback(SharedElementCallback callback); 174 | 175 | void setHasOptionsMenu(boolean hasMenu); 176 | 177 | void setInitialSavedState(@Nullable Fragment.SavedState state); 178 | 179 | void setMenuVisibility(boolean menuVisible); 180 | 181 | void setRetainInstance(boolean retain); 182 | 183 | void setTargetFragment(@Nullable Fragment fragment, int requestCode); 184 | 185 | boolean shouldShowRequestPermissionRationale(@NonNull String permission); 186 | 187 | void startActivity(Intent intent); 188 | 189 | void startActivity(Intent intent, @Nullable Bundle options); 190 | 191 | void startActivityForResult(Intent intent, int requestCode); 192 | 193 | void startActivityForResult(Intent intent, int requestCode, @Nullable Bundle options); 194 | 195 | void startIntentSenderForResult(IntentSender intent, int requestCode, @Nullable Intent fillInIntent, 196 | int flagsMask, int flagsValues, int extraFlags, Bundle options) throws SendIntentException; 197 | 198 | void startPostponedEnterTransition(); 199 | 200 | void super_dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args); 201 | 202 | boolean super_getAllowEnterTransitionOverlap(); 203 | 204 | boolean super_getAllowReturnTransitionOverlap(); 205 | 206 | Context super_getContext(); 207 | 208 | Object super_getEnterTransition(); 209 | 210 | Object super_getExitTransition(); 211 | 212 | LayoutInflater super_getLayoutInflater(@Nullable Bundle savedFragmentState); 213 | 214 | android.arch.lifecycle.Lifecycle super_getLifecycle(); 215 | 216 | LoaderManager super_getLoaderManager(); 217 | 218 | Object super_getReenterTransition(); 219 | 220 | Object super_getReturnTransition(); 221 | 222 | Object super_getSharedElementEnterTransition(); 223 | 224 | Object super_getSharedElementReturnTransition(); 225 | 226 | boolean super_getUserVisibleHint(); 227 | 228 | View super_getView(); 229 | 230 | android.arch.lifecycle.LifecycleOwner super_getViewLifecycleOwner(); 231 | 232 | LiveData super_getViewLifecycleOwnerLiveData(); 233 | 234 | ViewModelStore super_getViewModelStore(); 235 | 236 | void super_onActivityCreated(@Nullable Bundle savedInstanceState); 237 | 238 | void super_onActivityResult(int requestCode, int resultCode, Intent data); 239 | 240 | void super_onAttach(Context context); 241 | 242 | void super_onAttach(Activity activity); 243 | 244 | void super_onAttachFragment(Fragment childFragment); 245 | 246 | void super_onConfigurationChanged(Configuration newConfig); 247 | 248 | boolean super_onContextItemSelected(MenuItem item); 249 | 250 | void super_onCreate(@Nullable Bundle savedInstanceState); 251 | 252 | Animation super_onCreateAnimation(int transit, boolean enter, int nextAnim); 253 | 254 | Animator super_onCreateAnimator(int transit, boolean enter, int nextAnim); 255 | 256 | void super_onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo); 257 | 258 | void super_onCreateOptionsMenu(Menu menu, MenuInflater inflater); 259 | 260 | View super_onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, 261 | @Nullable Bundle savedInstanceState); 262 | 263 | void super_onDestroy(); 264 | 265 | void super_onDestroyOptionsMenu(); 266 | 267 | void super_onDestroyView(); 268 | 269 | void super_onDetach(); 270 | 271 | LayoutInflater super_onGetLayoutInflater(@Nullable Bundle savedInstanceState); 272 | 273 | void super_onHiddenChanged(boolean hidden); 274 | 275 | void super_onInflate(Context context, AttributeSet attrs, Bundle savedInstanceState); 276 | 277 | void super_onInflate(Activity activity, AttributeSet attrs, Bundle savedInstanceState); 278 | 279 | void super_onLowMemory(); 280 | 281 | void super_onMultiWindowModeChanged(boolean isInMultiWindowMode); 282 | 283 | boolean super_onOptionsItemSelected(MenuItem item); 284 | 285 | void super_onOptionsMenuClosed(Menu menu); 286 | 287 | void super_onPause(); 288 | 289 | void super_onPictureInPictureModeChanged(boolean isInPictureInPictureMode); 290 | 291 | void super_onPrepareOptionsMenu(Menu menu); 292 | 293 | void super_onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, 294 | @NonNull int[] grantResults); 295 | 296 | void super_onResume(); 297 | 298 | void super_onSaveInstanceState(@NonNull Bundle outState); 299 | 300 | void super_onStart(); 301 | 302 | void super_onStop(); 303 | 304 | void super_onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState); 305 | 306 | void super_onViewStateRestored(@Nullable Bundle savedInstanceState); 307 | 308 | void super_postponeEnterTransition(); 309 | 310 | void super_registerForContextMenu(View view); 311 | 312 | void super_setAllowEnterTransitionOverlap(boolean allow); 313 | 314 | void super_setAllowReturnTransitionOverlap(boolean allow); 315 | 316 | void super_setArguments(@Nullable Bundle args); 317 | 318 | void super_setEnterSharedElementCallback(SharedElementCallback callback); 319 | 320 | void super_setEnterTransition(@Nullable Object transition); 321 | 322 | void super_setExitSharedElementCallback(SharedElementCallback callback); 323 | 324 | void super_setExitTransition(@Nullable Object transition); 325 | 326 | void super_setHasOptionsMenu(boolean hasMenu); 327 | 328 | void super_setInitialSavedState(@Nullable Fragment.SavedState state); 329 | 330 | void super_setMenuVisibility(boolean menuVisible); 331 | 332 | void super_setReenterTransition(@Nullable Object transition); 333 | 334 | void super_setRetainInstance(boolean retain); 335 | 336 | void super_setReturnTransition(@Nullable Object transition); 337 | 338 | void super_setSharedElementEnterTransition(@Nullable Object transition); 339 | 340 | void super_setSharedElementReturnTransition(@Nullable Object transition); 341 | 342 | void super_setTargetFragment(@Nullable Fragment fragment, int requestCode); 343 | 344 | void super_setUserVisibleHint(boolean isVisibleToUser); 345 | 346 | boolean super_shouldShowRequestPermissionRationale(@NonNull String permission); 347 | 348 | void super_startActivity(Intent intent); 349 | 350 | void super_startActivity(Intent intent, @Nullable Bundle options); 351 | 352 | void super_startActivityForResult(Intent intent, int requestCode); 353 | 354 | void super_startActivityForResult(Intent intent, int requestCode, @Nullable Bundle options); 355 | 356 | void super_startIntentSenderForResult(IntentSender intent, int requestCode, @Nullable Intent fillInIntent, 357 | int flagsMask, int flagsValues, int extraFlags, Bundle options) throws SendIntentException; 358 | 359 | void super_startPostponedEnterTransition(); 360 | 361 | String super_toString(); 362 | 363 | void super_unregisterForContextMenu(View view); 364 | 365 | String toString(); 366 | 367 | void unregisterForContextMenu(View view); 368 | } 369 | -------------------------------------------------------------------------------- /fragment/src/test/java/com/pascalwelsch/compositeandroid/fragment/FragmentPluginTest.java: -------------------------------------------------------------------------------- 1 | package com.pascalwelsch.compositeandroid.fragment; 2 | 3 | import static org.assertj.core.api.Java6Assertions.*; 4 | 5 | import android.os.Bundle; 6 | import android.support.annotation.Nullable; 7 | import org.junit.*; 8 | 9 | public class FragmentPluginTest { 10 | 11 | public static class TestFragmentPlugin extends FragmentPlugin { 12 | 13 | private boolean mGotCalled; 14 | 15 | @Override 16 | public void onCreate(@Nullable final Bundle savedInstanceState) { 17 | super.onCreate(savedInstanceState); 18 | mGotCalled = true; 19 | } 20 | } 21 | 22 | public static class TestDialogFragmentPlugin extends DialogFragmentPlugin { 23 | 24 | private boolean mGotCalled; 25 | 26 | private boolean mShowsCalled; 27 | 28 | @Override 29 | public void onCreate(@Nullable final Bundle savedInstanceState) { 30 | super.onCreate(savedInstanceState); 31 | mGotCalled = true; 32 | } 33 | 34 | @Override 35 | public boolean getShowsDialog() { 36 | final boolean showsDialog = super.getShowsDialog(); 37 | mShowsCalled = true; 38 | return showsDialog; 39 | } 40 | } 41 | 42 | public static class TestDialogFragment extends CompositeDialogFragment { 43 | 44 | private final TestDialogFragmentPlugin mDialogPlugin; 45 | 46 | private final TestFragmentPlugin mPlugin; 47 | 48 | public TestDialogFragment() { 49 | mPlugin = new TestFragmentPlugin(); 50 | addPlugin(mPlugin); 51 | 52 | mDialogPlugin = new TestDialogFragmentPlugin(); 53 | addPlugin(mDialogPlugin); 54 | } 55 | } 56 | 57 | public static class TestFragment extends CompositeFragment { 58 | 59 | private final TestFragmentPlugin mPlugin; 60 | 61 | public TestFragment() { 62 | mPlugin = new TestFragmentPlugin(); 63 | addPlugin(mPlugin); 64 | } 65 | } 66 | 67 | @Test 68 | public void testCallBaseMethod() throws Exception { 69 | final TestFragment testFragment = new TestFragment(); 70 | assertThat(testFragment.mPlugin.mGotCalled).isFalse(); 71 | testFragment.onCreate(null); 72 | assertThat(testFragment.mPlugin.mGotCalled).isTrue(); 73 | 74 | final TestDialogFragment testDialogFragment = new TestDialogFragment(); 75 | assertThat(testDialogFragment.mPlugin.mGotCalled).isFalse(); 76 | assertThat(testDialogFragment.mDialogPlugin.mGotCalled).isFalse(); 77 | testDialogFragment.onCreate(null); 78 | assertThat(testDialogFragment.mPlugin.mGotCalled).isTrue(); 79 | assertThat(testDialogFragment.mDialogPlugin.mGotCalled).isTrue(); 80 | } 81 | 82 | @Test 83 | public void testCallDialogFragmentMethod() throws Exception { 84 | 85 | final TestDialogFragment testDialogFragment = new TestDialogFragment(); 86 | assertThat(testDialogFragment.mDialogPlugin.mShowsCalled).isFalse(); 87 | testDialogFragment.getShowsDialog(); 88 | assertThat(testDialogFragment.mDialogPlugin.mShowsCalled).isTrue(); 89 | 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /generator/.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | .DS_Store 3 | 4 | # Built application files 5 | *.apk 6 | *.ap_ 7 | 8 | # Files for the ART/Dalvik VM 9 | *.dex 10 | 11 | # Java class files 12 | *.class 13 | 14 | # Generated files 15 | bin/ 16 | gen/ 17 | out/ 18 | 19 | # Gradle files 20 | .gradle/ 21 | build/ 22 | 23 | # Local configuration file (sdk path, etc) 24 | local.properties 25 | 26 | # Log Files 27 | *.log 28 | 29 | # Android Studio Navigation editor temp files 30 | .navigation/ 31 | 32 | # Android Studio captures folder 33 | captures/ 34 | 35 | # Intellij 36 | *.iml 37 | **/.idea/* -------------------------------------------------------------------------------- /generator/README.md: -------------------------------------------------------------------------------- 1 | # Composite Generator 2 | 3 | Manually maintaining all methods of `AppCompatActivity` and the `SupportFragment` is impossible. Autogenerated code is the solution. 4 | 5 | This generator parses plain `.java` files. 6 | Even easier: It parses java files I generated with Android Studio. 7 | By extending the `Activity` and override all possible methods (keeping the JavaDoc) Android Studio generates a simple to parse file. 8 | This is by far the easiest solution. Using reflection doesn't work because reading the variable names and JavaDoc isn't possible. 9 | 10 | After generating the input files with this method the generator parses the `.java` file using RegEx. 11 | The RegEx for parsing methods of a java file isn't perfect but as long as it works it's fine. 12 | Once it breaks it can be adjusted. 13 | It doesn't parse the parts perfectly. 14 | I.e. the method visiblity doesn't get parsed or the method body can only be a single statement (which is the generated `super` call). 15 | 16 | Once parsed writing the sourcecode is easy. 17 | I considered using [javapoet](https://github.com/square/javapoet) but it didn't allow me to write plain text into a file. 18 | Parsing the content out of a JavaDoc to put it into the javapoet javadoc method was too hard. 19 | With this easy solution I copy the javadoc for every method as I read it in the input file. 20 | 21 | The output is readable and valid but not perfectly formatted. 22 | I reformat the code using Android Studio and everything is done. 23 | As the project grows the formatting takes longer and longer but the generator output is valid and it's possible to work with it while developing. 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /generator/build.gradle: -------------------------------------------------------------------------------- 1 | group 'com.pascalwelsch.compositeandroid' 2 | version '1.0-SNAPSHOT' 3 | 4 | buildscript { 5 | ext.kotlin_version = '1.2.50' 6 | 7 | repositories { 8 | mavenCentral() 9 | jcenter() 10 | } 11 | dependencies { 12 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 13 | } 14 | } 15 | 16 | apply plugin: 'kotlin' 17 | 18 | allprojects { 19 | repositories { 20 | mavenCentral() 21 | jcenter() 22 | } 23 | } 24 | 25 | dependencies { 26 | compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" 27 | compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" 28 | } 29 | repositories { 30 | mavenCentral() 31 | } -------------------------------------------------------------------------------- /generator/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/passsy/CompositeAndroid/80dec3eed5a72a0f18e1dd880d646fa3af002d93/generator/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /generator/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Aug 16 12:46:19 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip 7 | -------------------------------------------------------------------------------- /generator/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn ( ) { 37 | echo "$*" 38 | } 39 | 40 | die ( ) { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 165 | if [[ "$(uname)" == "Darwin" ]] && [[ "$HOME" == "$PWD" ]]; then 166 | cd "$(dirname "$0")" 167 | fi 168 | 169 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 170 | -------------------------------------------------------------------------------- /generator/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /generator/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'generator' 2 | 3 | -------------------------------------------------------------------------------- /generator/src/main/java/Main.java: -------------------------------------------------------------------------------- 1 | public class Main { 2 | 3 | public static void main(String[] args) { 4 | System.out.println("Hello Java"); 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /generator/src/main/kotlin/Generator.kt: -------------------------------------------------------------------------------- 1 | import types.generateActivity 2 | import types.generateFragments 3 | 4 | const val inPath = "../blueprints/src/main/java/com/pascalwelsch/compositeandroid/blueprints/" 5 | 6 | val autoGeneratedJavadocHeader = """ 7 | /** 8 | * This code was auto-generated by the CompositeAndroid generator 9 | * 10 | * @author Pascal Welsch 11 | */ 12 | """.trim() 13 | 14 | fun main(args: Array) { 15 | generateActivity() 16 | generateFragments() 17 | } -------------------------------------------------------------------------------- /generator/src/main/kotlin/parse/AnalyzedJavaFile.kt: -------------------------------------------------------------------------------- 1 | package parse 2 | 3 | data class AnalyzedJavaFile( 4 | val imports: String, 5 | var methods: List 6 | ) 7 | 8 | data class AnalyzedJavaMethod( 9 | val name: String, 10 | val visibility: String, 11 | val genericReturnType: String?, 12 | val returnType: String, 13 | val javadoc: String? = null, 14 | private val rawAnnotations: String? = null, 15 | val rawParameters: String?, 16 | val exceptions: String? = null) { 17 | 18 | val parameterTypes: List by lazy { 19 | val paramTypes = mutableListOf(); 20 | if (rawParameters != null && !rawParameters.trim().isEmpty()) { 21 | rawParameters.split(',').forEach { 22 | it.trim() 23 | val split = it.split(' ') 24 | val type = split.elementAt(split.size - 2).trim() 25 | paramTypes.add(type.toBoxedType()) 26 | } 27 | } 28 | paramTypes.toList() 29 | } 30 | 31 | val parameterNames: List by lazy { 32 | val paramNames = mutableListOf(); 33 | if (rawParameters != null && !rawParameters.trim().isEmpty()) { 34 | rawParameters.split(',').forEach { 35 | it.trim() 36 | val split = it.split(' ') 37 | paramNames.add(split.last()) 38 | } 39 | } 40 | paramNames.toList() 41 | } 42 | 43 | val annotations: List by lazy { 44 | rawAnnotations?.let { 45 | it.split("\n") 46 | .map { it.trim() } 47 | .filter { it.isNotEmpty() } 48 | } ?: emptyList() 49 | } 50 | 51 | val boxedReturnType: String by lazy { 52 | returnType.toBoxedType() 53 | } 54 | 55 | private fun String.toBoxedType(): String { 56 | return when (this) { 57 | "boolean" -> "Boolean" 58 | "int" -> "Integer" 59 | "void" -> "Void" 60 | // add others when needed 61 | else -> this 62 | } 63 | } 64 | 65 | 66 | val exceptionType: String? by lazy { 67 | var result: String? = null 68 | if (!exceptions.isNullOrBlank()) { 69 | result = exceptions!!.substringAfterLast("throws").trim().split(" ")[0] 70 | } 71 | result 72 | } 73 | 74 | val throws: Boolean = exceptionType != null 75 | val signature: String = "#$name($rawParameters):$returnType" 76 | 77 | val rawParametersBoxed: List by lazy { 78 | if (rawParameters != null && !rawParameters.trim().isEmpty()) { 79 | rawParameters.split(',').map { 80 | it.trim() 81 | val split = it.split(' ') 82 | val type = split.elementAt(split.size - 2).trim() 83 | val name = split.elementAt(split.size - 1).trim() 84 | "final ${type.toBoxedType()} $name" 85 | }.toList() 86 | } else { 87 | emptyList() 88 | } 89 | } 90 | } -------------------------------------------------------------------------------- /generator/src/main/kotlin/parse/JavaFileParser.kt: -------------------------------------------------------------------------------- 1 | package parse 2 | 3 | import java.io.File 4 | 5 | 6 | fun parseJavaFile(file: File): AnalyzedJavaFile { 7 | val source = file.readText() 8 | 9 | val importMatches = "(?:import.*;)" 10 | .toRegex(RegexOption.MULTILINE) 11 | .findAll(source) 12 | 13 | val builder = StringBuilder(); 14 | for (match in importMatches.iterator()) { 15 | val groups = match.groups 16 | if (groups.size > 0) { 17 | builder.append(groups[0]?.value?.trim()) 18 | builder.append("\n") 19 | } 20 | } 21 | val imports = builder.toString() 22 | 23 | 24 | // findAll runs out of memory 25 | val methodMatches = mutableListOf() 26 | var pos = 0 27 | while (true) { 28 | // for regex101 https://regex101.com/r/D7gTv3/2 29 | // (?:\n*?((?:\h*?\/\*\*\h*[\w@]*\n*){1}(?:\h*\*.*\n*)*?(?:\h*\*\/){1})\n)*((?:^\h*\@.*\n)*?)?\h*((?:\w* )*)(.* )?([\w\.<>]*(?:\[\])?) (\w*)\(((?:\n*[^\)]*)*)\)([^\{]*)\{((?:.*\n)(?:\n*(?: .*\n))*)\ \}\n* 30 | val methodsMatch = "(?:\n*?((?:\\h*?\\/\\*\\*\\h*[\\w@]*\n*){1}(?:\\h*\\*.*\n*)*?(?:\\h*\\*\\/){1})\n)*((?:^\\h*\\@.*\n)*?)?\\h*((?:\\w* )*)(.* )?([\\w\\.<>]*(?:\\[\\])?) (\\w*)\\(((?:\n*[^\\)]*)*)\\)([^\\{]*)\\{((?:.*\n)(?:\n*(?: .*\n))*)\\ \\}\n*" 31 | .toRegex(RegexOption.MULTILINE) 32 | .find(source, pos) 33 | 34 | if (methodsMatch == null) { 35 | break 36 | } 37 | methodMatches.add(methodsMatch) 38 | pos = methodsMatch.range.last 39 | } 40 | 41 | 42 | val methods = mutableListOf() 43 | 44 | for (match in methodMatches) { 45 | val groups = match.groups 46 | 47 | val javadoc = groups[1]?.value 48 | val annotations = groups[2]?.value 49 | val visibility = groups[3]?.value ?: "" 50 | val genericTypeDefinition = groups[4]?.value ?: "" 51 | val returnType = groups[5]?.value!! 52 | val name = groups[6]?.value!! 53 | val parameters = groups[7]?.value?.replace("\n", " ")?.replace(",\\h+".toRegex(), ", ") 54 | val throws = groups[8]?.value?.replace("\n", " ")?.replace(",\\h+".toRegex(), ", ")?.trim() 55 | 56 | val paramNames = mutableListOf() 57 | val paramType = mutableListOf() 58 | if (parameters != null) { 59 | if (!parameters.trim().isEmpty()) { 60 | parameters.split(',').forEach { 61 | it.trim() 62 | val split = it.split(' ') 63 | paramNames.add(split.last()) 64 | paramType.add(split.elementAt(split.size - 2)) 65 | } 66 | } 67 | } 68 | 69 | methods.add(AnalyzedJavaMethod(name, visibility, genericTypeDefinition, returnType, 70 | javadoc, annotations, parameters, throws)) 71 | } 72 | 73 | 74 | return AnalyzedJavaFile(imports, methods) 75 | } 76 | -------------------------------------------------------------------------------- /generator/src/main/kotlin/types/Activity.kt: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import inPath 4 | import parse.parseJavaFile 5 | import writer.writeComposite 6 | import writer.writeDelegate 7 | import writer.writeInterface 8 | import writer.writePlugin 9 | import java.io.File 10 | 11 | 12 | private val outPath = "../activity/src/main/java/com/pascalwelsch/compositeandroid/" 13 | 14 | fun main(args: Array) { 15 | generateActivity() 16 | } 17 | 18 | fun generateActivity() { 19 | var activity = parseJavaFile(File("$inPath/BlueprintActivity.java")) 20 | 21 | // filter methods with special implementations 22 | val filteredMethods = activity.methods 23 | .filterNot { it.name.contains("NonConfigurationInstance") } 24 | activity = activity.copy(methods = filteredMethods) 25 | 26 | writeComposite(outPath, 27 | activity, 28 | "activity", 29 | "CompositeActivity", 30 | "AppCompatActivity implements ICompositeActivity", 31 | delegateClassName = "ActivityDelegate", 32 | pluginClassName = "ActivityPlugin", 33 | transform = replaceExtraData, 34 | additionalImports = """ 35 | |import android.support.v4.app.SupportActivity; 36 | |import android.support.v7.app.ActionBarDrawerToggle; 37 | |import android.support.v7.app.ActionBarDrawerToggle.Delegate; 38 | |import android.support.v4.app.SupportActivity.ExtraData; 39 | """.replaceIndentByMargin(), 40 | addCodeToClass = activity_custom_nonConfigurationInstance_handling) 41 | 42 | writeDelegate(outPath, 43 | activity, 44 | "activity", 45 | "ActivityDelegate", 46 | "ICompositeActivity", 47 | "ActivityPlugin", 48 | extends = "AbstractDelegate", 49 | transform = replaceExtraData, 50 | additionalImports = """ 51 | |import java.util.ListIterator; 52 | |import android.support.v4.app.SupportActivity; 53 | |import android.support.v7.app.ActionBarDrawerToggle; 54 | |import android.support.v7.app.ActionBarDrawerToggle.Delegate; 55 | |import android.support.v4.app.SupportActivity.ExtraData; 56 | """.replaceIndentByMargin(), 57 | addCodeToClass = delegate_custom_nonConfigurationInstance_handling) 58 | 59 | writePlugin(outPath, 60 | "Activity", 61 | activity, 62 | "activity", 63 | "ActivityPlugin", 64 | extends = "AbstractPlugin", 65 | transform = replaceExtraData, 66 | additionalImports = """ 67 | |import android.support.v4.app.SupportActivity; 68 | |import android.support.v4.app.SupportActivity.ExtraData; 69 | """.replaceIndentByMargin(), 70 | addCodeToClass = plugin_custom_nonConfigurationInstance_handling) 71 | 72 | writeInterface(outPath, 73 | activity, 74 | "activity", 75 | "ICompositeActivity", 76 | "LayoutInflater.Factory2, Window.Callback, KeyEvent.Callback, View.OnCreateContextMenuListener, ComponentCallbacks2, ActivityCompat.OnRequestPermissionsResultCallback, AppCompatCallback, ActionBarDrawerToggle.DelegateProvider", 77 | addCodeToClass = interface_custom_nonConfigurationInstance_handling, 78 | transform = replaceExtraData, 79 | additionalImports = """ 80 | |import android.content.*; 81 | |import android.support.v4.app.ActivityCompat; 82 | |import android.support.v4.app.Fragment; 83 | |import android.support.v4.app.FragmentManager; 84 | |import android.support.v4.app.LoaderManager; 85 | |import android.support.v4.app.SharedElementCallback; 86 | |import android.support.v4.app.SupportActivity; 87 | |import android.support.v7.app.*; 88 | |import android.support.v7.app.ActionBarDrawerToggle.Delegate; 89 | |import android.support.v4.app.SupportActivity.ExtraData; 90 | """.replaceIndentByMargin()) 91 | } 92 | val replaceExtraData: (String) -> String = { 93 | it.replace(" ExtraData", " SupportActivity.ExtraData") 94 | .replace("(ExtraData)", "(SupportActivity.ExtraData)") 95 | .replace(") { 17 | generateFragments() 18 | } 19 | 20 | fun generateFragments() { 21 | 22 | val fragment = parseJavaFile(File("$inPath/BlueprintFragment.java")) 23 | 24 | generateFragment(fragment) 25 | 26 | generateDialogFragment(fragment) 27 | } 28 | 29 | 30 | val replaceAmbitiousTypesWithFullPackageNames: (String) -> String = { 31 | it.replace(" SavedState", " Fragment.SavedState") 32 | .replace("(SavedState)", "(Fragment.SavedState)") 33 | .replace(" String)? = null, 16 | superClassPluginNames: List = listOf(), 17 | superClassDelegateName: String = "", 18 | delegateClassName: String, 19 | pluginClassName: String, 20 | superClassInputFile: AnalyzedJavaFile? = null, 21 | addCodeToClass: String? = null) { 22 | 23 | val allPluginTypes = mutableListOf(pluginClassName) 24 | allPluginTypes.addAll(superClassPluginNames) 25 | 26 | 27 | fun addPlugins(): String { 28 | return allPluginTypes 29 | .distinct() 30 | .map { 31 | """ 32 | | 33 | |public Removable addPlugin(final $it plugin) { 34 | | return delegate.addPlugin(plugin); 35 | |} 36 | | 37 | |public List add${it}s(@NonNull final $it... plugins) { 38 | | final List removables = new ArrayList<>(plugins.length); 39 | | for (final $it plugin : plugins) { 40 | | removables.add(delegate.addPlugin(plugin)); 41 | | } 42 | | return removables; 43 | |} 44 | | 45 | |public List add${it}s(@NonNull final Iterable plugins) { 46 | | final List removables = new ArrayList<>(); 47 | | for (final $it plugin : plugins) { 48 | | removables.add(delegate.addPlugin(plugin)); 49 | | } 50 | | return removables; 51 | |} 52 | """.replaceIndentByMargin(" ") 53 | } 54 | .joinToString("\n") 55 | } 56 | 57 | val sb = StringBuilder(""" 58 | |package com.pascalwelsch.compositeandroid.$javaPackage; 59 | | 60 | |import android.annotation.SuppressLint; 61 | | 62 | |import com.pascalwelsch.compositeandroid.core.*; 63 | | 64 | |${javaFile.imports} 65 | | 66 | |$additionalImports 67 | |import java.util.*; 68 | | 69 | |@SuppressWarnings({"unused", "deprecation", "JavadocReference", "WrongConstant", "RestrictedApi"}) 70 | |@SuppressLint({"MissingSuperCall", "NewApi"}) 71 | | 72 | |$autoGeneratedJavadocHeader 73 | |public class $javaClassName extends $extends { 74 | | 75 | | protected $delegateClassName delegate = new $delegateClassName(this); 76 | | 77 | |${addPlugins()} 78 | | 79 | """.replaceIndentByMargin()) 80 | 81 | val allMethods = mutableListOf() 82 | if (superClassInputFile != null) { 83 | allMethods.addAll(superClassInputFile.methods) 84 | } 85 | allMethods.addAll(javaFile.methods) 86 | val distinctMethods = allMethods.distinctBy { "${it.name} ${it.parameterTypes}" }.toMutableList() 87 | 88 | for (method in distinctMethods) with(method) { 89 | // override method, calling delegate 90 | sb.appendln(callDelegate()) 91 | 92 | // super method 93 | sb.appendln(callSuper()) 94 | } 95 | 96 | addCodeToClass?.let { sb.appendln(it) } 97 | 98 | sb.appendln("}") 99 | 100 | var output = sb.toString() 101 | if (transform != null) { 102 | output = transform(output) 103 | } 104 | 105 | val out = File("$outPath${javaPackage.replace('.', '/')}/$javaClassName.java") 106 | out.parentFile.mkdirs() 107 | out.printWriter().use { it.write(output) } 108 | System.out.println("wrote ${out.absolutePath}") 109 | } 110 | 111 | private fun AnalyzedJavaMethod.callSuper(): String { 112 | 113 | val returnWhenNotVoid = if (returnType != "void") "return " else "" 114 | val formattedAnnotations = annotations 115 | .filter { !it.contains("Override") } 116 | .joinToString("\n") 117 | 118 | return """ 119 | |${javadoc?.trim() ?: ""} 120 | |$formattedAnnotations 121 | |@Override 122 | |public $genericReturnType$returnType super_$name($rawParameters) $exceptions{ 123 | | ${returnWhenNotVoid}super.$name(${parameterNames.joinToString()}); 124 | |} 125 | """.replaceIndentByMargin(" ") 126 | } 127 | 128 | private fun AnalyzedJavaMethod.callDelegate(): String { 129 | 130 | val returnWhenNotVoid = if (returnType != "void") "return " else "" 131 | return """ 132 | | 133 | |${javadoc?.trim() ?: ""} 134 | |${annotations.joinToString("\n")} 135 | |public $genericReturnType$returnType $name($rawParameters) $exceptions{ 136 | |${""" 137 | | ${returnWhenNotVoid}delegate.$name(${parameterNames.joinToString()}); 138 | """.replaceIndentByMargin().let { it: String -> 139 | if (throws) """ 140 | | try { 141 | | $it 142 | | } catch(SuppressedException e){ 143 | | throw ($exceptionType) e.getCause(); 144 | | } 145 | """.replaceIndentByMargin() else it 146 | }} 147 | |} 148 | """.replaceIndentByMargin(" ") 149 | } 150 | 151 | -------------------------------------------------------------------------------- /generator/src/main/kotlin/writer/DelegateWriter.kt: -------------------------------------------------------------------------------- 1 | package writer 2 | 3 | import autoGeneratedJavadocHeader 4 | import parse.AnalyzedJavaFile 5 | import parse.AnalyzedJavaMethod 6 | import java.io.File 7 | 8 | 9 | fun writeDelegate(outPath: String, 10 | javaFile: AnalyzedJavaFile, 11 | javaPackage: String, 12 | javaClassName: String, 13 | compositeName: String, 14 | pluginName: String, 15 | extends: String, 16 | additionalImports: String? = null, 17 | transform: ((String) -> String)? = null, 18 | superClassPluginName: String = "", 19 | superClassDelegateName: String = "", 20 | superClassInputFile: AnalyzedJavaFile? = null, 21 | addCodeToClass: String? = null 22 | ) { 23 | 24 | 25 | val methodsSb = StringBuilder() 26 | 27 | 28 | val allMethods = mutableListOf() 29 | if (superClassInputFile != null) { 30 | allMethods.addAll(superClassInputFile.methods) 31 | } 32 | allMethods.addAll(javaFile.methods) 33 | val distinctMethods = allMethods.distinctBy { "${it.name} ${it.parameterTypes}" }.toMutableList() 34 | 35 | for (method in distinctMethods) { 36 | 37 | val isSameMethod: (AnalyzedJavaMethod) -> Boolean = { method.name == it.name && method.parameterTypes == it.parameterTypes } 38 | val definedInSuperComposite = superClassInputFile?.methods 39 | ?.filter(isSameMethod)?.isNotEmpty() ?: false 40 | val definedInComposite = javaFile.methods.filter(isSameMethod).isNotEmpty() 41 | val isVoid = method.returnType == "void" 42 | 43 | 44 | //methodsSb.appendln( 45 | // "//${method.signature} inSuperComposite: $definedInSuperComposite, inComposite: $definedInComposite") 46 | methodsSb.appendln(when { 47 | definedInComposite && !definedInSuperComposite && isVoid -> 48 | method.hook(pluginName) 49 | definedInComposite && !definedInSuperComposite && !isVoid -> 50 | method.callFunction(pluginName) 51 | 52 | definedInSuperComposite && isVoid -> 53 | method.forwardToDelegate(superClassDelegateName) 54 | definedInSuperComposite && !isVoid -> 55 | method.forwardToDelegateWithReturn(superClassDelegateName) 56 | 57 | else -> "// TODO ${method.signature} inSuperComposite: $definedInSuperComposite, inComposite: $definedInComposite" 58 | }) 59 | } 60 | 61 | fun superDelegateInitialization(): String { 62 | return if (superClassDelegateName.isEmpty()) "" else 63 | "m$superClassDelegateName = new $superClassDelegateName(${compositeName.toLowerCase()});" 64 | } 65 | 66 | fun superDelegateDeclaration(): String { 67 | return if (superClassDelegateName.isEmpty()) "" else 68 | "private final $superClassDelegateName m$superClassDelegateName;" 69 | } 70 | 71 | fun addSuperDelegatePlugin(): String { 72 | return if (superClassDelegateName.isEmpty()) "" else """ 73 | |public Removable addPlugin(final $superClassPluginName plugin) { 74 | | return m$superClassDelegateName.addPlugin(plugin); 75 | |} 76 | """.replaceIndentByMargin() 77 | } 78 | 79 | fun addPlugin(): String { 80 | return if (superClassDelegateName.isEmpty()) "" else """ 81 | |@Override 82 | |public Removable addPlugin(final $pluginName plugin) { 83 | | final Removable removable = super.addPlugin(plugin); 84 | | final Removable superRemovable = m$superClassDelegateName.addPlugin(plugin); 85 | | return new Removable() { 86 | | @Override 87 | | public void remove() { 88 | | removable.remove(); 89 | | superRemovable.remove(); 90 | | } 91 | | }; 92 | |} 93 | """.replaceIndentByMargin() 94 | } 95 | 96 | var activityDelegate = """ 97 | |package com.pascalwelsch.compositeandroid.$javaPackage; 98 | | 99 | |import com.pascalwelsch.compositeandroid.core.*; 100 | | 101 | |${javaFile.imports} 102 | | 103 | |${additionalImports ?: ""} 104 | | 105 | |$autoGeneratedJavadocHeader 106 | |public class $javaClassName extends $extends { 107 | | 108 | | ${superDelegateDeclaration()} 109 | | 110 | | public $javaClassName(final $compositeName ${compositeName.toLowerCase()}) { 111 | | super(${compositeName.toLowerCase()}); 112 | | ${superDelegateInitialization()} 113 | | } 114 | | 115 | |${addSuperDelegatePlugin().prependIndent()} 116 | |${addPlugin().prependIndent()} 117 | | 118 | |${methodsSb.toString()} 119 | | 120 | |${addCodeToClass ?: ""} 121 | | 122 | |} 123 | """.replaceIndentByMargin() 124 | 125 | if (transform != null) { 126 | activityDelegate = transform(activityDelegate) 127 | } 128 | 129 | val out = File("$outPath${javaPackage.replace('.', '/')}/$javaClassName.java") 130 | out.parentFile.mkdirs() 131 | out.printWriter().use { it.write(activityDelegate) } 132 | System.out.println("wrote ${out.absolutePath}") 133 | } 134 | 135 | fun AnalyzedJavaMethod.forwardToDelegate(delegateName: String): String { 136 | return """ 137 | | 138 | |public void $name($rawParameters) $exceptions { 139 | | m$delegateName.$name(${parameterNames.joinToString()}); 140 | |} 141 | """.replaceIndentByMargin(" ") 142 | } 143 | 144 | fun AnalyzedJavaMethod.forwardToDelegateWithReturn(delegateName: String): String { 145 | return """ 146 | | 147 | |public $genericReturnType$returnType $name($rawParameters) $exceptions { 148 | | return m$delegateName.$name(${parameterNames.joinToString()}); 149 | |} 150 | """.replaceIndentByMargin(" ") 151 | } 152 | 153 | 154 | /*fun AnalyzedJavaMethod.hook(originalGetterName: String = "getOriginal()", 155 | pluginType: String = "Plugin"): String { 156 | 157 | 158 | val typedArgs = parameterTypes.mapIndexed { i, type -> "($type) args[$i]" } 159 | 160 | val varargs = if (parameterTypes.size == 1 161 | && parameterTypes[0].contains("[]")) { 162 | "new Object[]{${parameterNames[0]}}" 163 | } else parameterNames.joinToString() 164 | 165 | 166 | val sb = StringBuilder() 167 | sb.appendln(" public void $name($rawParameters) $exceptions {") 168 | sb.appendln( 169 | " callHook(\"$name(${parameterTypes.joinToString()})\", new PluginCallVoid<$pluginType>() {") 170 | sb.appendln(" @Override") 171 | sb.appendln( 172 | " public void call(final NamedSuperCall superCall, final $pluginType plugin, final Object... args) {") 173 | if (throws) sb.appendln(" try {") 174 | sb.appendln(" plugin.$name(${listOf("superCall").plus( 175 | typedArgs).joinToString()});") 176 | if (throws) { 177 | sb.appendln(" } catch ($exceptionType e) {") 178 | sb.appendln(" throw new SuppressedException(e);") 179 | sb.appendln(" }") 180 | } 181 | sb.appendln(" }") 182 | sb.appendln(" }, new SuperCallVoid() {") 183 | sb.appendln(" @Override") 184 | sb.appendln(" public void call(final Object... args) {") 185 | if (throws) sb.appendln(" try {") 186 | sb.appendln(" $originalGetterName.super_$name(${typedArgs.joinToString()});") 187 | if (throws) { 188 | sb.appendln(" } catch ($exceptionType e) {") 189 | sb.appendln(" throw new SuppressedException(e);") 190 | sb.appendln(" }") 191 | } 192 | sb.appendln(" }") 193 | sb.appendln( 194 | " }${if (parameterNames.size > 0) ", " else ""}$varargs);") 195 | sb.appendln(" }") 196 | 197 | return sb.toString(); 198 | }*/ 199 | 200 | fun AnalyzedJavaMethod.hook(pluginType: String = "Plugin"): String { 201 | 202 | val varargs = if (parameterNames.size == 1 && parameterNames[0].contains("[]")) { 203 | "new Object[]{${parameterNames[0]}}" 204 | } else parameterNames.joinToString() 205 | 206 | val genericTypeCallType = if (parameterTypes.isEmpty()) "" else "<${parameterTypes.joinToString()}>" 207 | 208 | return """ 209 | |public void $name($rawParameters) $exceptions { 210 | | if (mPlugins.isEmpty()) { 211 | |${"getOriginal().super_$name($varargs);" 212 | .wrapWithTryCatch(exceptionType).prependIndent(" ")} 213 | | return; 214 | | } 215 | | 216 | | final ListIterator<$pluginType> iterator = mPlugins.listIterator(mPlugins.size()); 217 | | 218 | | final CallVoid${parameterNames.size}$genericTypeCallType superCall = new CallVoid${parameterNames.size}$genericTypeCallType("$name(${parameterTypes.joinToString()})") { 219 | | 220 | | @Override 221 | | public void call(${rawParametersBoxed.joinToString()}) { 222 | | if (iterator.hasPrevious()) { 223 | |${"iterator.previous().$name(this${if (parameterNames.size > 0) ", " else ""}$varargs);" 224 | .wrapWithTryCatch(exceptionType).prependIndent(" ")} 225 | | } else { 226 | |${"getOriginal().super_$name($varargs);" 227 | .wrapWithTryCatch(exceptionType).prependIndent(" ")} 228 | | } 229 | | } 230 | | }; 231 | | superCall.call($varargs); 232 | |} 233 | """.replaceIndentByMargin(" ") 234 | } 235 | 236 | fun AnalyzedJavaMethod.callFunction(pluginType: String = "Plugin"): String { 237 | val typedArgs = parameterTypes.mapIndexed { i, type -> "($type) args[$i]" } 238 | 239 | val varargs = if (parameterNames.size == 1 && parameterNames[0].contains("[]")) { 240 | "new Object[]{${parameterNames[0]}}" 241 | } else parameterNames.joinToString() 242 | 243 | val genericTypes = mutableListOf().apply { 244 | add(boxedReturnType) 245 | addAll(parameterTypes) 246 | } 247 | val genericTypeCallType = "<${genericTypes.joinToString()}>" 248 | 249 | return """ 250 | |public $genericReturnType$returnType $name($rawParameters) $exceptions { 251 | | if (mPlugins.isEmpty()) { 252 | |${"return getOriginal().super_$name($varargs);" 253 | .wrapWithTryCatch(exceptionType).prependIndent(" ")} 254 | | } 255 | | 256 | | final ListIterator<$pluginType> iterator = mPlugins.listIterator(mPlugins.size()); 257 | | 258 | | final CallFun${parameterNames.size}$genericTypeCallType superCall = new CallFun${parameterNames.size}$genericTypeCallType("$name(${parameterTypes.joinToString()})") { 259 | | 260 | | @Override 261 | | public $boxedReturnType call(${rawParametersBoxed.joinToString()}) { 262 | | if (iterator.hasPrevious()) { 263 | |${"return iterator.previous().$name(this${if (parameterNames.size > 0) ", " else ""}$varargs);" 264 | .wrapWithTryCatch(exceptionType).prependIndent(" ")} 265 | | } else { 266 | |${"return getOriginal().super_$name($varargs);" 267 | .wrapWithTryCatch(exceptionType).prependIndent(" ")} 268 | | } 269 | | } 270 | | }; 271 | | return superCall.call($varargs); 272 | |} 273 | """.replaceIndentByMargin(" ") 274 | } 275 | 276 | fun String.wrapWithTryCatch(exceptionType: String?): String = 277 | if (exceptionType == null) this else """ 278 | |try{ 279 | | $this 280 | |} catch ($exceptionType e) { 281 | | throw new SuppressedException(e); 282 | |} 283 | """.replaceIndentByMargin() 284 | 285 | 286 | fun AnalyzedJavaMethod.notImplemented(): String { 287 | val sb = StringBuilder() 288 | sb.appendln(" public $genericReturnType$returnType $name($rawParameters) {") 289 | sb.appendln(" //TODO not implemented") 290 | sb.appendln(" return null;") 291 | sb.appendln(" }") 292 | return sb.toString(); 293 | } 294 | 295 | -------------------------------------------------------------------------------- /generator/src/main/kotlin/writer/InterfaceWriter.kt: -------------------------------------------------------------------------------- 1 | package writer 2 | 3 | import autoGeneratedJavadocHeader 4 | import parse.AnalyzedJavaFile 5 | import parse.AnalyzedJavaMethod 6 | import java.io.File 7 | 8 | 9 | fun writeInterface(outPath: String, 10 | javaFile: AnalyzedJavaFile, 11 | javaPackage: String, 12 | javaClassName: String, 13 | extends: String? = null, 14 | additionalImports: String = "", 15 | transform: ((String) -> String)? = null, 16 | superClassPluginNames: List = listOf(), 17 | superClassDelegateName: String = "", 18 | superClassInfputFile: AnalyzedJavaFile? = null, 19 | addCodeToClass: String? = null) { 20 | 21 | val sb = StringBuilder(""" 22 | |package com.pascalwelsch.compositeandroid.$javaPackage; 23 | | 24 | |import com.pascalwelsch.compositeandroid.core.*; 25 | | 26 | |${javaFile.imports} 27 | | 28 | |$additionalImports 29 | | 30 | |$autoGeneratedJavadocHeader 31 | |public interface $javaClassName ${extends?.let { "extends $extends " } ?: ""}{ 32 | | 33 | | 34 | """.replaceIndentByMargin()) 35 | 36 | for (method in javaFile.methods) with(method) { 37 | sb.appendln(toInterface()) 38 | sb.appendln(toSuperInterface()) 39 | } 40 | 41 | addCodeToClass?.let { sb.appendln(it) } 42 | 43 | sb.appendln("}") 44 | 45 | var output = sb.toString() 46 | if (transform != null) { 47 | output = transform(output) 48 | } 49 | 50 | val out = File("$outPath${javaPackage.replace('.', '/')}/$javaClassName.java") 51 | out.parentFile.mkdirs() 52 | out.printWriter().use { it.write(output) } 53 | System.out.println("wrote ${out.absolutePath}") 54 | } 55 | 56 | 57 | private fun AnalyzedJavaMethod.toInterface(): String { 58 | return """ 59 | | 60 | |$genericReturnType$returnType $name($rawParameters) $exceptions; 61 | """.replaceIndentByMargin(" ") 62 | } 63 | 64 | private fun AnalyzedJavaMethod.toSuperInterface(): String { 65 | return """ 66 | | 67 | |$genericReturnType$returnType super_$name($rawParameters) $exceptions; 68 | """.replaceIndentByMargin(" ") 69 | } -------------------------------------------------------------------------------- /generator/src/main/kotlin/writer/PluginWriter.kt: -------------------------------------------------------------------------------- 1 | package writer 2 | 3 | import autoGeneratedJavadocHeader 4 | import parse.AnalyzedJavaFile 5 | import parse.AnalyzedJavaMethod 6 | import java.io.File 7 | 8 | fun writePlugin(outPath: String, 9 | normalClassName: String, 10 | javaFile: AnalyzedJavaFile, 11 | javaPackage: String, 12 | javaClassName: String, 13 | extends: String, 14 | additionalImports: String? = null, 15 | transform: ((String) -> String)? = null, 16 | superClassInputFile: AnalyzedJavaFile? = null, 17 | addCodeToClass: String? = null) { 18 | 19 | val sb = StringBuilder() 20 | for (method in javaFile.methods) with(method) { 21 | when (returnType) { 22 | "void" -> sb.append(method.callListener()) 23 | else -> sb.append(method.returnSuperListener()) 24 | } 25 | } 26 | val methods = sb.toString() 27 | 28 | 29 | val niceGetter: String = """ 30 | |public $normalClassName get$normalClassName() { 31 | | return ($normalClassName) getOriginal(); 32 | |} 33 | """.replaceIndentByMargin(" ") 34 | 35 | 36 | var code = """ 37 | |package com.pascalwelsch.compositeandroid.$javaPackage; 38 | | 39 | |import com.pascalwelsch.compositeandroid.core.*; 40 | | 41 | |${javaFile.imports} 42 | | 43 | |${additionalImports ?: ""} 44 | | 45 | |$autoGeneratedJavadocHeader 46 | |@SuppressWarnings("unused") 47 | |public class $javaClassName extends $extends { 48 | |$methods 49 | | 50 | |$niceGetter 51 | | 52 | |${addCodeToClass ?: ""} 53 | |} 54 | """.replaceIndentByMargin() 55 | 56 | if (transform != null) { 57 | code = transform(code) 58 | } 59 | 60 | val out = File("$outPath${javaPackage.replace('.', '/')}/$javaClassName.java") 61 | out.parentFile.mkdirs() 62 | out.printWriter().use { it.write(code) } 63 | System.out.println("wrote ${out.absolutePath}") 64 | } 65 | 66 | fun AnalyzedJavaMethod.returnSuperListener(): String { 67 | val genericTypes = mutableListOf().apply { 68 | add(boxedReturnType) 69 | addAll(parameterTypes) 70 | } 71 | 72 | return """ 73 | ${javadoc?.trim() ?: ""} 74 | public $genericReturnType$returnType $name($rawParameters) $exceptions{ 75 | verifyMethodCalledFromDelegate("$name(${parameterTypes.joinToString()})"); 76 | return ((CallFun${parameterNames.size}<${genericTypes.joinToString()}>) mSuperListeners.pop()).call(${parameterNames.joinToString()}); 77 | } 78 | 79 | $genericReturnType$returnType $name(final CallFun${parameterNames.size}<${genericTypes.joinToString()}> superCall ${if (parameterNames.isNotEmpty()) ", " else ""}$rawParameters) $exceptions{ 80 | synchronized (mSuperListeners) { 81 | mSuperListeners.push(superCall); 82 | return $name(${parameterNames.joinToString()}); 83 | } 84 | } 85 | """ 86 | } 87 | 88 | fun AnalyzedJavaMethod.callListener(): String { 89 | val genericTypeCallType = if(parameterTypes.isEmpty()) "" else "<${parameterTypes.joinToString()}>" 90 | 91 | return """ 92 | ${javadoc?.trim() ?: ""} 93 | public void $name($rawParameters) $exceptions{ 94 | verifyMethodCalledFromDelegate("$name(${parameterTypes.joinToString()})"); 95 | ((CallVoid${parameterNames.size}$genericTypeCallType) mSuperListeners.pop()).call(${parameterNames.joinToString()}); 96 | } 97 | 98 | void $name(final CallVoid${parameterNames.size}$genericTypeCallType superCall ${if (parameterNames.isNotEmpty()) ", " else ""}$rawParameters) $exceptions{ 99 | synchronized (mSuperListeners) { 100 | mSuperListeners.push(superCall); 101 | $name(${parameterNames.joinToString()}); 102 | } 103 | } 104 | """ 105 | } 106 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | org.gradle.jvmargs=-Xmx3072m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/passsy/CompositeAndroid/80dec3eed5a72a0f18e1dd880d646fa3af002d93/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Aug 16 12:32:04 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn ( ) { 37 | echo "$*" 38 | } 39 | 40 | die ( ) { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save ( ) { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /sample/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /sample/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion COMPILE_SDK_VERSION 5 | 6 | defaultConfig { 7 | applicationId "com.pascalwelsch.compositeandroid" 8 | minSdkVersion MIN_SDK_VERSION 9 | targetSdkVersion TARGET_SDK_VERSION 10 | versionCode 1 11 | versionName "1.0" 12 | } 13 | 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | 21 | lintOptions { 22 | abortOnError false 23 | } 24 | 25 | dexOptions { 26 | preDexLibraries = !ciBuild 27 | javaMaxHeapSize "2g" 28 | } 29 | } 30 | 31 | dependencies { 32 | compile project(':activity') 33 | compile project(':fragment') 34 | 35 | testCompile 'junit:junit:4.12' 36 | compile "com.android.support:appcompat-v7:$supportLibraryVersion" 37 | } 38 | -------------------------------------------------------------------------------- /sample/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /usr/local/Cellar/android-sdk/23.0.2/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /sample/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /sample/src/main/java/com/pascalwelsch/compositeandroid/ActivityTracking.java: -------------------------------------------------------------------------------- 1 | package com.pascalwelsch.compositeandroid; 2 | 3 | import com.pascalwelsch.compositeandroid.activity.ActivityPlugin; 4 | 5 | import android.util.Log; 6 | 7 | public class ActivityTracking extends ActivityPlugin { 8 | 9 | private static final String TAG = ActivityTracking.class.getSimpleName(); 10 | 11 | @Override 12 | public void onResume() { 13 | super.onResume(); 14 | Log.v(TAG, "#1 onResume()"); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /sample/src/main/java/com/pascalwelsch/compositeandroid/ActivityTracking2.java: -------------------------------------------------------------------------------- 1 | package com.pascalwelsch.compositeandroid; 2 | 3 | import com.pascalwelsch.compositeandroid.activity.ActivityPlugin; 4 | 5 | import android.util.Log; 6 | 7 | public class ActivityTracking2 extends ActivityPlugin { 8 | 9 | private static final String TAG = ActivityTracking2.class.getSimpleName(); 10 | 11 | @Override 12 | public void onResume() { 13 | super.onResume(); 14 | Log.v(TAG, "#2 onResume()"); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /sample/src/main/java/com/pascalwelsch/compositeandroid/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.pascalwelsch.compositeandroid; 2 | 3 | import com.pascalwelsch.compositeandroid.activity.CompositeActivity; 4 | import com.pascalwelsch.compositeandroid.fragment.TestFragment; 5 | 6 | import android.os.Bundle; 7 | import android.view.View; 8 | 9 | public class MainActivity extends CompositeActivity { 10 | 11 | public MainActivity() { 12 | addPlugin(new ActivityTracking()); 13 | addPlugin(new ActivityTracking2()); 14 | } 15 | 16 | @Override 17 | public void onCreate(Bundle savedInstanceState) { 18 | super.onCreate(savedInstanceState); 19 | setContentView(R.layout.activity_main); 20 | 21 | final View btn = findViewById(R.id.button); 22 | assert btn != null; 23 | btn.setOnClickListener(new View.OnClickListener() { 24 | @Override 25 | public void onClick(final View v) { 26 | new TestFragment().show(getSupportFragmentManager(), "dialog"); 27 | } 28 | }); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /sample/src/main/java/com/pascalwelsch/compositeandroid/fragment/DialogTracking.java: -------------------------------------------------------------------------------- 1 | package com.pascalwelsch.compositeandroid.fragment; 2 | 3 | import android.support.v4.app.FragmentManager; 4 | import android.util.Log; 5 | 6 | public class DialogTracking extends DialogFragmentPlugin { 7 | 8 | private static final String TAG = DialogTracking.class.getSimpleName(); 9 | 10 | private String mName; 11 | 12 | public DialogTracking(final String name) { 13 | 14 | mName = name; 15 | } 16 | 17 | @Override 18 | public void onResume() { 19 | super.onResume(); 20 | 21 | Log.v(TAG, mName + " onResume()"); 22 | } 23 | 24 | @Override 25 | public void onStart() { 26 | super.onStart(); 27 | 28 | Log.v(TAG, mName + " onStart()"); 29 | } 30 | 31 | @Override 32 | public void onStop() { 33 | super.onStop(); 34 | Log.v(TAG, mName + " onStop()"); 35 | } 36 | 37 | @Override 38 | public void show(final FragmentManager manager, final String tag) { 39 | super.show(manager, tag); 40 | Log.v(TAG, mName + "show(fm, tag)"); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /sample/src/main/java/com/pascalwelsch/compositeandroid/fragment/FragmentTracking.java: -------------------------------------------------------------------------------- 1 | package com.pascalwelsch.compositeandroid.fragment; 2 | 3 | import android.util.Log; 4 | 5 | public class FragmentTracking extends FragmentPlugin { 6 | 7 | private static final String TAG = FragmentTracking.class.getSimpleName(); 8 | 9 | @Override 10 | public void onResume() { 11 | super.onResume(); 12 | 13 | Log.v(TAG, "#1 onResume()"); 14 | } 15 | 16 | @Override 17 | public void onStart() { 18 | super.onStart(); 19 | 20 | Log.v(TAG, "#1 onStart()"); 21 | } 22 | 23 | @Override 24 | public void onStop() { 25 | super.onStop(); 26 | Log.v(TAG, "#1 onStop()"); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /sample/src/main/java/com/pascalwelsch/compositeandroid/fragment/TestFragment.java: -------------------------------------------------------------------------------- 1 | package com.pascalwelsch.compositeandroid.fragment; 2 | 3 | import android.support.annotation.Nullable; 4 | import android.util.Log; 5 | import android.view.View; 6 | import android.widget.TextView; 7 | 8 | public class TestFragment extends CompositeDialogFragment { 9 | 10 | private static final String TAG = TestFragment.class.getSimpleName(); 11 | 12 | public TestFragment() { 13 | addPlugin(new FragmentTracking()); 14 | addPlugin(new DialogTracking("single added")); 15 | 16 | addDialogFragmentPlugins( 17 | new DialogTracking("multiple dialog plugins #1"), 18 | new DialogTracking("multiple dialog plugins #2")); 19 | addFragmentPlugins(new FragmentTracking(), new DialogTracking("multiple fragment plugins")); 20 | } 21 | 22 | @Nullable 23 | @Override 24 | public View getView() { 25 | final TextView textView = new TextView(getContext()); 26 | textView.setText("hello world"); 27 | return textView; 28 | } 29 | 30 | @Override 31 | public void onResume() { 32 | super.onResume(); 33 | Log.v(TAG, "fragment onResume()"); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /sample/src/main/java/com/pascalwelsch/compositeandroid/performance/CompositePerformanceTestActivity.java: -------------------------------------------------------------------------------- 1 | package com.pascalwelsch.compositeandroid.performance; 2 | 3 | import com.pascalwelsch.compositeandroid.R; 4 | import com.pascalwelsch.compositeandroid.activity.ActivityPlugin; 5 | import com.pascalwelsch.compositeandroid.activity.CompositeActivity; 6 | 7 | import android.annotation.SuppressLint; 8 | import android.content.res.Resources; 9 | import android.os.Bundle; 10 | import android.support.annotation.Nullable; 11 | import android.support.v7.app.AppCompatDelegate; 12 | import android.util.Log; 13 | import android.view.View; 14 | 15 | import java.io.File; 16 | import java.util.ArrayList; 17 | import java.util.List; 18 | 19 | public class CompositePerformanceTestActivity extends CompositeActivity { 20 | 21 | private static class PerformanceTestPlugin extends ActivityPlugin { 22 | 23 | private final String mName; 24 | 25 | public PerformanceTestPlugin(final String name) { 26 | 27 | mName = name; 28 | } 29 | 30 | @Override 31 | public void onSaveInstanceState(final Bundle outState) { 32 | super.onSaveInstanceState(outState); 33 | outState.putString(mName, mName); 34 | } 35 | } 36 | 37 | private static class PerformanceTestPluginOverridden extends PerformanceTestPlugin { 38 | 39 | public PerformanceTestPluginOverridden(final String name) { 40 | super(name); 41 | } 42 | 43 | @Override 44 | public Resources getResources() { 45 | return super.getResources(); 46 | } 47 | 48 | @Override 49 | public File getCacheDir() { 50 | return super.getCacheDir(); 51 | } 52 | } 53 | 54 | private static final String TAG = CompositePerformanceTestActivity.class.getSimpleName(); 55 | 56 | public static final int ITERATIONS = 50000; 57 | 58 | private final String mName = "0"; 59 | 60 | public CompositePerformanceTestActivity() { 61 | addPlugin(new PerformanceTestPluginOverridden("1")); 62 | addPlugin(new PerformanceTestPluginOverridden("2")); 63 | addPlugin(new PerformanceTestPluginOverridden("3")); 64 | addPlugin(new PerformanceTestPluginOverridden("4")); 65 | addPlugin(new PerformanceTestPluginOverridden("5")); 66 | /*addPlugin(new PerformanceTestPlugin("1")); 67 | addPlugin(new PerformanceTestPlugin("2")); 68 | addPlugin(new PerformanceTestPlugin("3")); 69 | addPlugin(new PerformanceTestPlugin("4")); 70 | addPlugin(new PerformanceTestPlugin("5"));*/ 71 | } 72 | 73 | 74 | public File getFastCacheDir() { 75 | return super_getCacheDir(); 76 | } 77 | 78 | public Resources getFastResources() { 79 | return super_getResources(); 80 | } 81 | 82 | @Override 83 | public void onCreate(@Nullable final Bundle savedInstanceState) { 84 | super.onCreate(savedInstanceState); 85 | setContentView(R.layout.activity_performance); 86 | final View start = findViewById(R.id.start); 87 | assert start != null; 88 | start.setOnClickListener(new View.OnClickListener() { 89 | @Override 90 | public void onClick(final View v) { 91 | runPerformanceTest(); 92 | } 93 | }); 94 | } 95 | 96 | @Override 97 | public void onSaveInstanceState(final Bundle outState) { 98 | super.onSaveInstanceState(outState); 99 | outState.putString(mName, mName); 100 | } 101 | 102 | private void runPerformanceTest() { 103 | 104 | String number = "5 plugins: "; 105 | 106 | System.gc(); 107 | final int iterations = 1; 108 | float duration = 0; 109 | 110 | duration = 0; 111 | for (int i = 0; i < iterations; i++) { 112 | duration += testGetCacheDir(); 113 | } 114 | duration /= iterations; 115 | Log.v(TAG, number + "getCacheDir() in " + duration + "ms"); 116 | 117 | duration = 0; 118 | for (int i = 0; i < iterations; i++) { 119 | duration += testFastGetCacheDir(); 120 | } 121 | duration /= iterations; 122 | Log.v(TAG, number + "getCacheDir() in " + duration + "ms (direct)"); 123 | 124 | duration = 0; 125 | for (int i = 0; i < iterations; i++) { 126 | duration += testGetResources(); 127 | } 128 | duration /= iterations; 129 | Log.v(TAG, number + "getResources() in " + duration + "ms"); 130 | 131 | duration = 0; 132 | for (int i = 0; i < iterations; i++) { 133 | duration += testFastGetResources(); 134 | } 135 | duration /= iterations; 136 | Log.v(TAG, number + "getResources() in " + duration + "ms (direct)"); 137 | 138 | 139 | duration = 0; 140 | for (int i = 0; i < iterations; i++) { 141 | duration += testOnSaveInstanceState(); 142 | } 143 | duration /= iterations; 144 | Log.v(TAG, number + "onSaveInstanceState() in " + duration + "ms"); 145 | 146 | 147 | duration = 0; 148 | for (int i = 0; i < iterations; i++) { 149 | duration += testFastOnSaveInstanceState(); 150 | } 151 | duration /= iterations; 152 | Log.v(TAG, number + "onSaveInstanceState() in " + duration + "ms (direct)"); 153 | } 154 | 155 | private long testGetCacheDir() { 156 | final List results = new ArrayList<>(); 157 | final long start = System.currentTimeMillis(); 158 | for (long i = 0; i < ITERATIONS; i++) { 159 | final File file = getCacheDir(); 160 | results.add(file); 161 | } 162 | return System.currentTimeMillis() - start; 163 | } 164 | 165 | private long testFastGetCacheDir() { 166 | final List results = new ArrayList<>(); 167 | final long start = System.currentTimeMillis(); 168 | for (long i = 0; i < ITERATIONS; i++) { 169 | final File file = getFastCacheDir(); 170 | results.add(file); 171 | } 172 | return System.currentTimeMillis() - start; 173 | } 174 | 175 | private long testGetDelegate() { 176 | final List results = new ArrayList<>(); 177 | final long start = System.currentTimeMillis(); 178 | for (long i = 0; i < ITERATIONS; i++) { 179 | final AppCompatDelegate delegate = getDelegate(); 180 | results.add(delegate); 181 | } 182 | return System.currentTimeMillis() - start; 183 | } 184 | 185 | private long testGetFile() { 186 | final List results = new ArrayList<>(); 187 | final long start = System.currentTimeMillis(); 188 | for (long i = 0; i < ITERATIONS; i++) { 189 | final File file = getFilesDir(); 190 | results.add(file); 191 | } 192 | return System.currentTimeMillis() - start; 193 | } 194 | 195 | private long testGetResources() { 196 | final List results = new ArrayList<>(); 197 | final long start = System.currentTimeMillis(); 198 | for (long i = 0; i < ITERATIONS; i++) { 199 | final Resources resources = getResources(); 200 | results.add(resources); 201 | } 202 | return System.currentTimeMillis() - start; 203 | } 204 | 205 | private long testFastGetResources() { 206 | final List results = new ArrayList<>(); 207 | final long start = System.currentTimeMillis(); 208 | for (long i = 0; i < ITERATIONS; i++) { 209 | final Resources resources = getFastResources(); 210 | results.add(resources); 211 | } 212 | return System.currentTimeMillis() - start; 213 | } 214 | 215 | @SuppressLint("Assert") 216 | private long testOnSaveInstanceState() { 217 | final List results = new ArrayList<>(); 218 | final long start = System.currentTimeMillis(); 219 | for (long i = 0; i < ITERATIONS; i++) { 220 | final Bundle outState = new Bundle(); 221 | onSaveInstanceState(outState); 222 | results.add(outState); 223 | } 224 | final long end = System.currentTimeMillis(); 225 | 226 | assert results.get(0).getString("1") == "1"; 227 | assert results.get(0).getString("20") == "20"; 228 | assert results.get(results.size()).getString("1") == "1"; 229 | assert results.get(results.size()).getString("20") == "20"; 230 | 231 | return end - start; 232 | } 233 | 234 | @SuppressLint("Assert") 235 | private long testFastOnSaveInstanceState() { 236 | final List results = new ArrayList<>(); 237 | final long start = System.currentTimeMillis(); 238 | for (long i = 0; i < ITERATIONS; i++) { 239 | final Bundle outState = new Bundle(); 240 | super_onSaveInstanceState(outState); 241 | results.add(outState); 242 | } 243 | final long end = System.currentTimeMillis(); 244 | 245 | assert results.get(0).getString("1") == "1"; 246 | assert results.get(0).getString("20") == "20"; 247 | assert results.get(results.size()).getString("1") == "1"; 248 | assert results.get(results.size()).getString("20") == "20"; 249 | 250 | return end - start; 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /sample/src/main/java/com/pascalwelsch/compositeandroid/performance/PerformanceTestActivity.java: -------------------------------------------------------------------------------- 1 | package com.pascalwelsch.compositeandroid.performance; 2 | 3 | import com.pascalwelsch.compositeandroid.R; 4 | 5 | import android.annotation.SuppressLint; 6 | import android.content.res.Resources; 7 | import android.os.Bundle; 8 | import android.support.annotation.Nullable; 9 | import android.support.v7.app.AppCompatDelegate; 10 | import android.util.Log; 11 | import android.view.View; 12 | 13 | import java.io.File; 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | 17 | public class PerformanceTestActivity extends PerformanceTestActivity1 { 18 | 19 | private static final String TAG = PerformanceTestActivity.class.getSimpleName(); 20 | 21 | public static final int ITERATIONS = 50000; 22 | 23 | private final String mName = "0"; 24 | 25 | @Override 26 | public Resources getResources() { 27 | return super.getResources(); 28 | } 29 | 30 | @Override 31 | protected void onCreate(@Nullable final Bundle savedInstanceState) { 32 | super.onCreate(savedInstanceState); 33 | setContentView(R.layout.activity_performance); 34 | final View start = findViewById(R.id.start); 35 | assert start != null; 36 | start.setOnClickListener(new View.OnClickListener() { 37 | @Override 38 | public void onClick(final View v) { 39 | runPerformanceTest(); 40 | } 41 | }); 42 | } 43 | 44 | @Override 45 | protected void onSaveInstanceState(final Bundle outState) { 46 | super.onSaveInstanceState(outState); 47 | outState.putString(mName, mName); 48 | } 49 | 50 | private void runPerformanceTest() { 51 | 52 | String number = "5 stages: "; 53 | 54 | System.gc(); 55 | final int iterations = 1; 56 | float duration = 0; 57 | 58 | duration = 0; 59 | for (int i = 0; i < iterations; i++) { 60 | duration += testGetCacheDir(); 61 | } 62 | duration /= iterations; 63 | Log.v(TAG, number + "getCacheDir() in " + duration + "ms"); 64 | 65 | duration = 0; 66 | for (int i = 0; i < iterations; i++) { 67 | duration += testGetResources(); 68 | } 69 | duration /= iterations; 70 | Log.v(TAG, number + "getResources() in " + duration + "ms"); 71 | 72 | duration = 0; 73 | for (int i = 0; i < iterations; i++) { 74 | duration += testGetDelegate(); 75 | } 76 | duration /= iterations; 77 | Log.v(TAG, number + "getDelegate() in " + duration + "ms"); 78 | 79 | duration = 0; 80 | for (int i = 0; i < iterations; i++) { 81 | duration += testGetFile(); 82 | } 83 | duration /= iterations; 84 | Log.v(TAG, number + "getFile() in " + duration + "ms"); 85 | 86 | duration = 0; 87 | for (int i = 0; i < iterations; i++) { 88 | duration += testOnSaveInstanceState(); 89 | } 90 | duration /= iterations; 91 | Log.v(TAG, number + "onSaveInstanceState() in " + duration + "ms"); 92 | 93 | } 94 | 95 | private long testGetCacheDir() { 96 | final List results = new ArrayList<>(); 97 | final long start = System.currentTimeMillis(); 98 | for (long i = 0; i < ITERATIONS; i++) { 99 | final File file = getCacheDir(); 100 | results.add(file); 101 | } 102 | return System.currentTimeMillis() - start; 103 | } 104 | 105 | private long testGetDelegate() { 106 | final List results = new ArrayList<>(); 107 | final long start = System.currentTimeMillis(); 108 | for (long i = 0; i < ITERATIONS; i++) { 109 | final AppCompatDelegate delegate = getDelegate(); 110 | results.add(delegate); 111 | } 112 | return System.currentTimeMillis() - start; 113 | } 114 | 115 | private long testGetFile() { 116 | final List results = new ArrayList<>(); 117 | final long start = System.currentTimeMillis(); 118 | for (long i = 0; i < ITERATIONS; i++) { 119 | final File file = getFilesDir(); 120 | results.add(file); 121 | } 122 | return System.currentTimeMillis() - start; 123 | } 124 | 125 | private long testGetResources() { 126 | final List results = new ArrayList<>(); 127 | final long start = System.currentTimeMillis(); 128 | for (long i = 0; i < ITERATIONS; i++) { 129 | final Resources resources = getResources(); 130 | results.add(resources); 131 | } 132 | return System.currentTimeMillis() - start; 133 | } 134 | 135 | @SuppressLint("Assert") 136 | private long testOnSaveInstanceState() { 137 | final List results = new ArrayList<>(); 138 | final long start = System.currentTimeMillis(); 139 | for (long i = 0; i < ITERATIONS; i++) { 140 | final Bundle outState = new Bundle(); 141 | onSaveInstanceState(outState); 142 | results.add(outState); 143 | } 144 | final long end = System.currentTimeMillis(); 145 | 146 | assert results.get(0).getString("1") == "1"; 147 | assert results.get(0).getString("20") == "20"; 148 | assert results.get(results.size()).getString("1") == "1"; 149 | assert results.get(results.size()).getString("20") == "20"; 150 | 151 | return end - start; 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /sample/src/main/java/com/pascalwelsch/compositeandroid/performance/PerformanceTestActivity1.java: -------------------------------------------------------------------------------- 1 | package com.pascalwelsch.compositeandroid.performance; 2 | 3 | import android.content.res.Resources; 4 | import android.os.Bundle; 5 | 6 | public class PerformanceTestActivity1 extends PerformanceTestActivity2 { 7 | 8 | private final String mName = "1"; 9 | 10 | @Override 11 | public Resources getResources() { 12 | return super.getResources(); 13 | } 14 | 15 | @Override 16 | protected void onSaveInstanceState(final Bundle outState) { 17 | super.onSaveInstanceState(outState); 18 | outState.putString(mName, mName); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /sample/src/main/java/com/pascalwelsch/compositeandroid/performance/PerformanceTestActivity2.java: -------------------------------------------------------------------------------- 1 | package com.pascalwelsch.compositeandroid.performance; 2 | 3 | import android.content.res.Resources; 4 | import android.os.Bundle; 5 | 6 | public class PerformanceTestActivity2 extends PerformanceTestActivity3 { 7 | 8 | private final String mName = "2"; 9 | 10 | @Override 11 | public Resources getResources() { 12 | return super.getResources(); 13 | } 14 | 15 | @Override 16 | protected void onSaveInstanceState(final Bundle outState) { 17 | super.onSaveInstanceState(outState); 18 | outState.putString(mName, mName); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /sample/src/main/java/com/pascalwelsch/compositeandroid/performance/PerformanceTestActivity3.java: -------------------------------------------------------------------------------- 1 | package com.pascalwelsch.compositeandroid.performance; 2 | 3 | import android.content.res.Resources; 4 | import android.os.Bundle; 5 | 6 | public class PerformanceTestActivity3 extends PerformanceTestActivity4 { 7 | 8 | private final String mName = "3"; 9 | 10 | @Override 11 | public Resources getResources() { 12 | return super.getResources(); 13 | } 14 | 15 | @Override 16 | protected void onSaveInstanceState(final Bundle outState) { 17 | super.onSaveInstanceState(outState); 18 | outState.putString(mName, mName); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /sample/src/main/java/com/pascalwelsch/compositeandroid/performance/PerformanceTestActivity4.java: -------------------------------------------------------------------------------- 1 | package com.pascalwelsch.compositeandroid.performance; 2 | 3 | import android.content.res.Resources; 4 | import android.os.Bundle; 5 | 6 | public class PerformanceTestActivity4 extends PerformanceTestActivity5 { 7 | 8 | private final String mName = "4"; 9 | 10 | @Override 11 | public Resources getResources() { 12 | return super.getResources(); 13 | } 14 | 15 | @Override 16 | protected void onSaveInstanceState(final Bundle outState) { 17 | super.onSaveInstanceState(outState); 18 | outState.putString(mName, mName); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /sample/src/main/java/com/pascalwelsch/compositeandroid/performance/PerformanceTestActivity5.java: -------------------------------------------------------------------------------- 1 | package com.pascalwelsch.compositeandroid.performance; 2 | 3 | import android.content.res.Resources; 4 | import android.os.Bundle; 5 | import android.support.v7.app.AppCompatActivity; 6 | 7 | public class PerformanceTestActivity5 extends AppCompatActivity { 8 | 9 | private final String mName = "5"; 10 | 11 | @Override 12 | public Resources getResources() { 13 | return super.getResources(); 14 | } 15 | 16 | @Override 17 | protected void onSaveInstanceState(final Bundle outState) { 18 | super.onSaveInstanceState(outState); 19 | outState.putString(mName, mName); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 17 | 18 |