├── app
├── .gitignore
├── src
│ └── main
│ │ ├── res
│ │ ├── values
│ │ │ ├── strings.xml
│ │ │ ├── colors.xml
│ │ │ └── styles.xml
│ │ ├── mipmap-hdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ └── layout
│ │ │ └── activity_main.xml
│ │ ├── java
│ │ ├── com
│ │ │ └── hananrh
│ │ │ │ └── app
│ │ │ │ ├── grandpa
│ │ │ │ └── GrandpaActivity.java
│ │ │ │ ├── papa
│ │ │ │ └── PapaActivity.java
│ │ │ │ └── MainActivity.java
│ │ └── .gitignore
│ │ └── AndroidManifest.xml
├── proguard-rules.pro
└── build.gradle
├── retainer
├── .gitignore
├── gradle.properties
├── src
│ └── main
│ │ ├── res
│ │ └── values
│ │ │ └── strings.xml
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ ├── com
│ │ └── hananrh
│ │ │ └── retainer
│ │ │ ├── IClassRetainer.java
│ │ │ ├── RetainedFieldsMapHolder.java
│ │ │ └── Retainer.java
│ │ └── .gitignore
├── build.gradle
└── proguard-rules.pro
├── bintray_sync.bat
├── retainer-annotations
├── .gitignore
├── gradle.properties
├── build.gradle
└── src
│ └── main
│ └── java
│ ├── .gitignore
│ └── com
│ └── hananrh
│ └── retainer
│ └── Retain.java
├── retainer-compiler
├── .gitignore
├── gradle.properties
├── src
│ └── main
│ │ └── java
│ │ ├── .gitignore
│ │ └── com
│ │ └── hananrh
│ │ └── retainer
│ │ └── compiler
│ │ ├── enforcers
│ │ ├── AbsEnforcer.java
│ │ ├── EnclosingClassEnforcer.java
│ │ ├── ModifiersEnforcer.java
│ │ └── ContextEnforcer.java
│ │ ├── Logger.java
│ │ ├── Utils.java
│ │ └── RetainerProcessor.java
└── build.gradle
├── icon
└── logo.png
├── settings.gradle
├── .gitignore
├── .idea
└── vcs.xml
├── gradle.properties
├── gradle
├── java_mvn_push.gradle
└── android_mvn_push.gradle
├── gradlew.bat
├── README.md
└── gradlew
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/retainer/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/bintray_sync.bat:
--------------------------------------------------------------------------------
1 | gradlew bintrayUpload
--------------------------------------------------------------------------------
/retainer-annotations/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/retainer-compiler/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/icon/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hrofeh/retainer/HEAD/icon/logo.png
--------------------------------------------------------------------------------
/retainer/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_ARTIFACT_ID=retainer
2 | POM_NAME=Retainer
3 | POM_PACKAGING=aar
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':retainer-compiler', ':retainer-annotations', ':retainer', ':app'
2 |
--------------------------------------------------------------------------------
/retainer-compiler/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_NAME=Retainer Compiler
2 | POM_ARTIFACT_ID=retainer-compiler
3 | POM_PACKAGING=jar
--------------------------------------------------------------------------------
/retainer/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Retainer
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Test Application
3 |
4 |
--------------------------------------------------------------------------------
/retainer-annotations/gradle.properties:
--------------------------------------------------------------------------------
1 | POM_NAME=Retainer Annotations
2 | POM_ARTIFACT_ID=retainer-annotations
3 | POM_PACKAGING=jar
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hrofeh/retainer/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hrofeh/retainer/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hrofeh/retainer/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hrofeh/retainer/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hrofeh/retainer/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hrofeh/retainer/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hrofeh/retainer/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hrofeh/retainer/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hrofeh/retainer/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hrofeh/retainer/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | .DS_Store
7 | /build
8 | /captures
9 | .externalNativeBuild
10 | .gitignore
11 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/retainer-annotations/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'java'
2 |
3 | sourceCompatibility = rootProject.ext.sourceCompatibilityVersion
4 | targetCompatibility = rootProject.ext.targetCompatibilityVersion
5 |
6 | apply from: rootProject.file('gradle/java_mvn_push.gradle')
7 |
--------------------------------------------------------------------------------
/retainer/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
5 |
6 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/retainer/src/main/java/com/hananrh/retainer/IClassRetainer.java:
--------------------------------------------------------------------------------
1 | package com.hananrh.retainer;
2 |
3 | import android.support.v4.app.FragmentManager;
4 |
5 | /**
6 | * Created by Hanan on 2/7/2017.
7 | */
8 |
9 | public interface IClassRetainer {
10 |
11 | void restore(T target, FragmentManager manager);
12 |
13 | void retain(T target, FragmentManager manager);
14 | }
15 |
--------------------------------------------------------------------------------
/retainer-annotations/src/main/java/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by .ignore support plugin (hsz.mobi)
2 | ### Java template
3 | *.class
4 |
5 | # BlueJ files
6 | *.ctxt
7 |
8 | # Mobile Tools for Java (J2ME)
9 | .mtj.tmp/
10 |
11 | # Package Files #
12 | *.jar
13 | *.war
14 | *.ear
15 |
16 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
17 | hs_err_pid*
18 |
19 |
--------------------------------------------------------------------------------
/retainer-compiler/src/main/java/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by .ignore support plugin (hsz.mobi)
2 | ### Java template
3 | *.class
4 |
5 | # BlueJ files
6 | *.ctxt
7 |
8 | # Mobile Tools for Java (J2ME)
9 | .mtj.tmp/
10 |
11 | # Package Files #
12 | *.jar
13 | *.war
14 | *.ear
15 |
16 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
17 | hs_err_pid*
18 |
19 |
--------------------------------------------------------------------------------
/retainer-compiler/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'java'
2 |
3 | sourceCompatibility = rootProject.ext.sourceCompatibilityVersion
4 | targetCompatibility = rootProject.ext.targetCompatibilityVersion
5 |
6 | apply from: rootProject.file('gradle/java_mvn_push.gradle')
7 |
8 | dependencies {
9 | compile project(':retainer-annotations')
10 | compile deps.autoservice
11 | compile deps.javapoet
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/retainer-annotations/src/main/java/com/hananrh/retainer/Retain.java:
--------------------------------------------------------------------------------
1 | package com.hananrh.retainer;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | /**
9 | * Created by Hanan on 2/6/2017.
10 | */
11 |
12 | @Target(ElementType.FIELD)
13 | @Retention(RetentionPolicy.CLASS)
14 | public @interface Retain {
15 | }
16 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/retainer-compiler/src/main/java/com/hananrh/retainer/compiler/enforcers/AbsEnforcer.java:
--------------------------------------------------------------------------------
1 | package com.hananrh.retainer.compiler.enforcers;
2 |
3 | import com.hananrh.retainer.compiler.Logger;
4 |
5 | import java.lang.annotation.Annotation;
6 |
7 | import javax.lang.model.element.Element;
8 |
9 | /**
10 | * Created by Hanan on 2/13/2017.
11 | */
12 |
13 | public abstract class AbsEnforcer {
14 |
15 | protected Logger mLogger;
16 |
17 | public AbsEnforcer(Logger logger)
18 | {
19 | mLogger = logger;
20 | }
21 |
22 | public abstract boolean enforce(Class extends Annotation> annotation, Element element);
23 | }
24 |
--------------------------------------------------------------------------------
/app/src/main/java/com/hananrh/app/grandpa/GrandpaActivity.java:
--------------------------------------------------------------------------------
1 | package com.hananrh.app.grandpa;
2 |
3 | import android.os.Bundle;
4 | import android.support.v7.app.AppCompatActivity;
5 |
6 | import com.hananrh.retainer.Retain;
7 |
8 | /**
9 | * Created by Hanan on 2/13/2017.
10 | */
11 |
12 | public abstract class GrandpaActivity extends AppCompatActivity {
13 |
14 | @Retain
15 | protected boolean mBooleanToRetain;
16 |
17 | protected void onCreate(Bundle savedInstanceState)
18 | {
19 | super.onCreate(savedInstanceState);
20 | if (savedInstanceState == null)
21 | {
22 | mBooleanToRetain = true;
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/main/java/com/hananrh/app/papa/PapaActivity.java:
--------------------------------------------------------------------------------
1 | package com.hananrh.app.papa;
2 |
3 | import android.os.Bundle;
4 |
5 | import com.hananrh.app.grandpa.GrandpaActivity;
6 | import com.hananrh.retainer.Retain;
7 |
8 | /**
9 | * Created by Hanan on 2/13/2017.
10 | */
11 |
12 | public abstract class PapaActivity extends GrandpaActivity {
13 |
14 | @Retain
15 | protected String mStringToRetain;
16 |
17 | protected void onCreate(Bundle savedInstanceState)
18 | {
19 | super.onCreate(savedInstanceState);
20 | if (savedInstanceState == null)
21 | {
22 | mStringToRetain = "This is a retained string";
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/java/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by .ignore support plugin (hsz.mobi)
2 | ### Android template
3 | # Built application files
4 | *.apk
5 | *.ap_
6 |
7 | # Files for the ART/Dalvik VM
8 | *.dex
9 |
10 | # Java class files
11 | *.class
12 |
13 | # Generated files
14 | bin/
15 | gen/
16 | out/
17 |
18 | # Gradle files
19 | .gradle/
20 | build/
21 |
22 | # Local configuration file (sdk path, etc)
23 | local.properties
24 |
25 | # Proguard folder generated by Eclipse
26 | proguard/
27 |
28 | # Log Files
29 | *.log
30 |
31 | # Android Studio Navigation editor temp files
32 | .navigation/
33 |
34 | # Android Studio captures folder
35 | captures/
36 |
37 | # Intellij
38 | *.iml
39 | .idea/workspace.xml
40 | .idea/tasks.xml
41 | .idea/libraries
42 |
43 | # Keystore files
44 | *.jks
45 |
46 | # External native build folder generated in Android Studio 2.2 and later
47 | .externalNativeBuild
48 |
49 |
--------------------------------------------------------------------------------
/retainer/src/main/java/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by .ignore support plugin (hsz.mobi)
2 | ### Android template
3 | # Built application files
4 | *.apk
5 | *.ap_
6 |
7 | # Files for the ART/Dalvik VM
8 | *.dex
9 |
10 | # Java class files
11 | *.class
12 |
13 | # Generated files
14 | bin/
15 | gen/
16 | out/
17 |
18 | # Gradle files
19 | .gradle/
20 | build/
21 |
22 | # Local configuration file (sdk path, etc)
23 | local.properties
24 |
25 | # Proguard folder generated by Eclipse
26 | proguard/
27 |
28 | # Log Files
29 | *.log
30 |
31 | # Android Studio Navigation editor temp files
32 | .navigation/
33 |
34 | # Android Studio captures folder
35 | captures/
36 |
37 | # Intellij
38 | *.iml
39 | .idea/workspace.xml
40 | .idea/tasks.xml
41 | .idea/libraries
42 |
43 | # Keystore files
44 | *.jks
45 |
46 | # External native build folder generated in Android Studio 2.2 and later
47 | .externalNativeBuild
48 |
49 |
--------------------------------------------------------------------------------
/retainer/src/main/java/com/hananrh/retainer/RetainedFieldsMapHolder.java:
--------------------------------------------------------------------------------
1 | package com.hananrh.retainer;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.os.Bundle;
5 | import android.support.annotation.Nullable;
6 | import android.support.v4.app.Fragment;
7 |
8 | import java.util.HashMap;
9 | import java.util.Map;
10 |
11 | /**
12 | * Created by Hanan on 2/6/2017.
13 | */
14 |
15 | @SuppressLint("ValidFragment")
16 | public class RetainedFieldsMapHolder extends Fragment {
17 |
18 | private Map mMap;
19 |
20 | public RetainedFieldsMapHolder()
21 | {
22 | mMap = new HashMap<>();
23 | }
24 |
25 | @Override
26 | public void onCreate(@Nullable Bundle savedInstanceState)
27 | {
28 | super.onCreate(savedInstanceState);
29 | setRetainInstance(true);
30 | }
31 |
32 | public Map getMap()
33 | {
34 | return mMap;
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/retainer/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | android {
4 | compileSdkVersion rootProject.ext.compileSdkVersion
5 | buildToolsVersion rootProject.ext.buildToolsVersion
6 |
7 | defaultConfig {
8 | minSdkVersion rootProject.ext.minSdkVersion
9 |
10 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
11 |
12 | }
13 | compileOptions{
14 | sourceCompatibility = rootProject.ext.sourceCompatibilityVersion
15 | targetCompatibility = rootProject.ext.targetCompatibilityVersion
16 | }
17 | buildTypes {
18 | release {
19 | minifyEnabled false
20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
21 | }
22 | }
23 | }
24 |
25 | apply from: rootProject.file('gradle/android_mvn_push.gradle')
26 |
27 |
28 | dependencies {
29 | compile deps.supportCompat
30 | compile project(':retainer-annotations')
31 | }
32 |
--------------------------------------------------------------------------------
/app/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 C:\Android\sdk/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 |
19 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
--------------------------------------------------------------------------------
/retainer/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 C:\Android\sdk/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 |
19 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 25
5 | buildToolsVersion "25.0.0"
6 |
7 | defaultConfig {
8 | applicationId "com.hrh.app"
9 | minSdkVersion 15
10 | targetSdkVersion 25
11 | versionCode 1
12 | versionName "1.0"
13 |
14 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
15 |
16 | }
17 | buildTypes {
18 | release {
19 | minifyEnabled false
20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
21 | }
22 | }
23 | }
24 |
25 | dependencies {
26 | compile fileTree(dir: 'libs', include: ['*.jar'])
27 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
28 | exclude group: 'com.android.support', module: 'support-annotations'
29 | })
30 | compile 'com.android.support:appcompat-v7:25.1.0'
31 | compile 'com.android.support.constraint:constraint-layout:1.0.0-beta4'
32 | testCompile 'junit:junit:4.12'
33 |
34 | compile project(':retainer')
35 | annotationProcessor project(':retainer-compiler')
36 | }
37 |
--------------------------------------------------------------------------------
/retainer-compiler/src/main/java/com/hananrh/retainer/compiler/Logger.java:
--------------------------------------------------------------------------------
1 | package com.hananrh.retainer.compiler;
2 |
3 | import javax.annotation.processing.ProcessingEnvironment;
4 | import javax.lang.model.element.Element;
5 | import javax.tools.Diagnostic;
6 |
7 | /**
8 | * Created by Hanan on 2/13/2017.
9 | */
10 |
11 | public class Logger {
12 |
13 | private ProcessingEnvironment mEnv;
14 |
15 | Logger(ProcessingEnvironment processingEnvironment)
16 | {
17 | mEnv = processingEnvironment;
18 | }
19 |
20 | public void e(Element element, String message, Object... args)
21 | {
22 | printMessage(Diagnostic.Kind.ERROR, element, message, args);
23 | }
24 |
25 | public void d(Element element, String message, Object... args)
26 | {
27 | printMessage(Diagnostic.Kind.NOTE, element, message, args);
28 | }
29 |
30 | public void printMessage(Diagnostic.Kind kind, Element element, String message, Object[] args)
31 | {
32 | if (args.length > 0)
33 | {
34 | message = String.format(message, args);
35 | }
36 |
37 | mEnv.getMessager().printMessage(kind, message, element);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/retainer-compiler/src/main/java/com/hananrh/retainer/compiler/enforcers/EnclosingClassEnforcer.java:
--------------------------------------------------------------------------------
1 | package com.hananrh.retainer.compiler.enforcers;
2 |
3 | import com.hananrh.retainer.compiler.Logger;
4 |
5 | import java.lang.annotation.Annotation;
6 |
7 | import javax.lang.model.element.Element;
8 | import javax.lang.model.element.ElementKind;
9 | import javax.lang.model.element.Modifier;
10 |
11 | /**
12 | * Created by Hanan on 2/13/2017.
13 | */
14 |
15 | public class EnclosingClassEnforcer extends AbsEnforcer {
16 |
17 | public EnclosingClassEnforcer(Logger logger)
18 | {
19 | super(logger);
20 | }
21 |
22 | @Override
23 | public boolean enforce(Class extends Annotation> annotation, Element element)
24 | {
25 | Element classElement = element.getEnclosingElement();
26 | if (classElement.getKind() != ElementKind.CLASS ||
27 | classElement.getModifiers().contains(Modifier.PRIVATE))
28 | {
29 | mLogger.e(element, "@%s field %s.%s must be contained in a non-private class",
30 | annotation.getSimpleName(), classElement.getSimpleName(), element.getSimpleName());
31 | return false;
32 | }
33 | return true;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/retainer-compiler/src/main/java/com/hananrh/retainer/compiler/enforcers/ModifiersEnforcer.java:
--------------------------------------------------------------------------------
1 | package com.hananrh.retainer.compiler.enforcers;
2 |
3 | import com.hananrh.retainer.compiler.Logger;
4 |
5 | import java.lang.annotation.Annotation;
6 | import java.util.Set;
7 |
8 | import javax.lang.model.element.Element;
9 | import javax.lang.model.element.Modifier;
10 |
11 | /**
12 | * Created by Hanan on 2/13/2017.
13 | */
14 |
15 | public class ModifiersEnforcer extends AbsEnforcer {
16 |
17 | public ModifiersEnforcer(Logger logger)
18 | {
19 | super(logger);
20 | }
21 |
22 | @Override
23 | public boolean enforce(Class extends Annotation> annotation, Element element)
24 | {
25 | Set modifierList = element.getModifiers();
26 | for (Modifier modifier : modifierList)
27 | {
28 | if (modifier == Modifier.PRIVATE || modifier == Modifier.STATIC)
29 | {
30 | mLogger.e(element, "@%s field %s.%s cannot be defined private or static",
31 | annotation.getSimpleName(), element.getEnclosingElement().getSimpleName(), element.getSimpleName());
32 | return false;
33 | }
34 | }
35 | return true;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/app/src/main/java/com/hananrh/app/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.hananrh.app;
2 |
3 | import android.os.Bundle;
4 | import android.widget.TextView;
5 |
6 | import com.hananrh.app.papa.PapaActivity;
7 | import com.hananrh.retainer.Retain;
8 | import com.hananrh.retainer.Retainer;
9 |
10 | public class MainActivity extends PapaActivity {
11 |
12 | @Retain
13 | int mNumToTest;
14 |
15 | @Override
16 | protected void onCreate(Bundle savedInstanceState)
17 | {
18 | super.onCreate(savedInstanceState);
19 | setContentView(R.layout.activity_main);
20 | if (savedInstanceState == null)
21 | {
22 | mNumToTest = 2;
23 | }
24 | Retainer.setLogsEnabled(true);
25 | Retainer.restore(this);
26 | ((TextView) findViewById(R.id.text)).setText("Retained number is: " + mNumToTest);
27 | ((TextView) findViewById(R.id.text2)).setText("Retained string is: " + mStringToRetain);
28 | ((TextView) findViewById(R.id.text3)).setText("Retained boolean is: " + mBooleanToRetain);
29 | }
30 |
31 | @Override
32 | protected void onSaveInstanceState(Bundle outState)
33 | {
34 | super.onSaveInstanceState(outState);
35 | Retainer.retain(this);
36 | }
37 |
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/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 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
19 | REP=maven
20 | GROUP=com.hrh
21 | VERSION_NAME=0.0.56
22 |
23 | POM_DESCRIPTION=Retain fields through configuration change
24 |
25 | SITE_URL=https://github.com/hananrh/retainer/
26 | GIT_URL=https://github.com/hananrh/retainer.git
27 |
28 | POM_LICENCE_NAME=The Apache Software License, Version 2.0
29 | POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt
30 | POM_LICENCE_DIST=repo
31 |
32 | POM_DEVELOPER_ID=hrh
33 | POM_DEVELOPER_NAME=Hanan Rofe Haim
34 | POM_DEVELOPER_EMAIL=hanan.rofe.haim@gmail.com
--------------------------------------------------------------------------------
/gradle/java_mvn_push.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: "com.jfrog.bintray"
2 | apply plugin: 'maven-publish'
3 | apply plugin: 'maven'
4 |
5 | // custom tasks for creating source/javadoc jars
6 | task sourcesJar(type: Jar, dependsOn: classes) {
7 | classifier = 'sources'
8 | from sourceSets.main.allSource
9 | }
10 |
11 | task javadocJar(type: Jar, dependsOn: javadoc) {
12 | classifier = 'javadoc'
13 | from javadoc.destinationDir
14 | }
15 |
16 | // add javadoc/source jar tasks as artifacts
17 | artifacts {
18 | archives sourcesJar, javadocJar
19 | }
20 |
21 | publishing {
22 | publications {
23 | MyPublication(MavenPublication) {
24 | from components.java
25 | groupId = GROUP
26 | artifactId = POM_ARTIFACT_ID
27 | version = VERSION_NAME
28 |
29 | artifact(sourcesJar)
30 | artifact(javadocJar)
31 | }
32 | }
33 | }
34 | Properties properties = new Properties()
35 | properties.load(project.rootProject.file('local.properties').newDataInputStream())
36 |
37 | bintray {
38 | user = properties.getProperty('bintray.user')
39 | key = properties.getProperty('bintray.apikey')
40 | publications = ['MyPublication']
41 | publish = true
42 | pkg {
43 | repo = REP
44 | name = POM_ARTIFACT_ID
45 | licenses = ['Apache-2.0']
46 | vcsUrl = GIT_URL
47 | version {
48 | name = VERSION_NAME
49 | desc = POM_DESCRIPTION
50 | released = new Date()
51 | }
52 | }
53 | }
--------------------------------------------------------------------------------
/retainer-compiler/src/main/java/com/hananrh/retainer/compiler/enforcers/ContextEnforcer.java:
--------------------------------------------------------------------------------
1 | package com.hananrh.retainer.compiler.enforcers;
2 |
3 | import com.hananrh.retainer.compiler.Logger;
4 | import com.hananrh.retainer.compiler.Utils;
5 |
6 | import java.lang.annotation.Annotation;
7 |
8 | import javax.lang.model.element.Element;
9 | import javax.lang.model.type.DeclaredType;
10 |
11 | /**
12 | * Created by Hanan on 2/13/2017.
13 | */
14 |
15 | public class ContextEnforcer extends AbsEnforcer {
16 |
17 | public ContextEnforcer(Logger logger)
18 | {
19 | super(logger);
20 | }
21 |
22 | @Override
23 | public boolean enforce(Class extends Annotation> annotation, Element element)
24 | {
25 | return enforce(annotation, element, element);
26 | }
27 |
28 | private boolean enforce(Class extends Annotation> annotation, Element orgElement, Element element)
29 | {
30 | String className = element.getSimpleName().toString();
31 | if (element.asType().getKind().isPrimitive() || className.equals(Object.class.getSimpleName()))
32 | {
33 | return true;
34 | }
35 | if (className.equals("Context"))
36 | {
37 | mLogger.e(orgElement, "@%s field %s.%s cannot be an android context",
38 | annotation.getSimpleName(), orgElement.getEnclosingElement().getSimpleName(), orgElement.getSimpleName());
39 | return false;
40 | }
41 | return enforce(annotation, orgElement, Utils.getSuperClassElement(
42 | ((DeclaredType) element.asType()).asElement()));
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
20 |
21 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/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 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
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 Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/gradle/android_mvn_push.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.github.dcendents.android-maven'
2 | apply plugin: "com.jfrog.bintray"
3 |
4 | version = VERSION_NAME
5 | group= GROUP
6 |
7 | install {
8 | repositories.mavenInstaller {
9 | // This generates POM.xml with proper parameters
10 | pom {
11 | project {
12 | packaging POM_PACKAGING
13 |
14 | // Add your description here
15 | name POM_ARTIFACT_ID
16 | description = POM_DESCRIPTION
17 | url SITE_URL
18 |
19 | // Set your license
20 | licenses {
21 | license {
22 | name 'The Apache Software License, Version 2.0'
23 | url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
24 | }
25 | }
26 | developers {
27 | developer {
28 | id POM_DEVELOPER_ID
29 | name POM_DEVELOPER_NAME
30 | email POM_DEVELOPER_EMAIL
31 | }
32 | }
33 | scm {
34 | connection GIT_URL
35 | developerConnection GIT_URL
36 | url SITE_URL
37 | }
38 | }
39 |
40 | whenConfigured { pom ->
41 | pom.dependencies.forEach { dep ->
42 | if (dep.getVersion() == "unspecified" && dep.groupId == rootProject.name) {
43 | dep.setGroupId(GROUP)
44 | dep.setVersion(VERSION_NAME)
45 | }
46 | }
47 | }
48 | }
49 | }
50 | }
51 |
52 | task sourcesJar(type: Jar) {
53 | from android.sourceSets.main.java.srcDirs
54 | classifier = 'sources'
55 | }
56 |
57 | task javadoc(type: Javadoc) {
58 | source = android.sourceSets.main.java.srcDirs
59 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
60 | }
61 |
62 | task javadocJar(type: Jar, dependsOn: javadoc) {
63 | classifier = 'javadoc'
64 | from javadoc.destinationDir
65 | }
66 | artifacts {
67 | archives javadocJar
68 | archives sourcesJar
69 | }
70 |
71 | Properties properties = new Properties()
72 | properties.load(project.rootProject.file('local.properties').newDataInputStream())
73 |
74 | // https://github.com/bintray/gradle-bintray-plugin
75 | bintray {
76 | user = properties.getProperty("bintray.user")
77 | key = properties.getProperty("bintray.apikey")
78 |
79 | configurations = ['archives']
80 | pkg {
81 | repo = REP
82 | name = POM_ARTIFACT_ID
83 | websiteUrl = SITE_URL
84 | vcsUrl = GIT_URL
85 | licenses = ["Apache-2.0"]
86 | publish = true
87 | }
88 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Retainer
2 | ============
3 | 
4 |
5 | An android library which enables activity/fragment fields retention through configuration changes (e.g screen rotatio) by generating boilerplate code for you.
6 |
7 | * Retain any object and maintain reference through configuration changes (no need for slow serialization-deserialization process).
8 | * Makes MVVM implemntation cleaner and shorter by retaining the ViewModel.
9 | * Simple and straight forward use.
10 |
11 | ```java
12 | class MainActivity extends AppCompatActivity{
13 |
14 | @Retain
15 | ViewModel mViewModel; //This field will be retained through configuration changes
16 |
17 | @Override
18 | protected void onCreate(Bundle savedInstanceState)
19 | {
20 | super.onCreate(savedInstanceState);
21 | setContentView(R.layout.activity_main);
22 | Retainer.restore(this);
23 | }
24 | @Override
25 | protected void onSaveInstanceState(Bundle outState)
26 | {
27 | super.onSaveInstanceState(outState);
28 | Retainer.retain(this);
29 | }
30 |
31 | //Calls to retain() and restore() can be made from a base activity class
32 | }
33 |
34 | ```
35 |
36 | Download
37 | --------
38 |
39 | ```groovy
40 | dependencies {
41 | compile 'com.hrh:retainer:0.0.56'
42 | annotationProcessor 'com.hrh:retainer-compiler:0.0.56'
43 | }
44 | ```
45 |
46 | How does it work
47 | --------
48 | Retainer creates and handles an headless retained fragment which wraps a mapping of all your @Retain fields, as simple as that.
49 |
50 | In order to keep runtime impact minimal, Retainer uses mostly code generation while reflection is only used for constructing and caching a retainer object for your activity/fragment when it is first created.
51 |
52 | Watch out for memory (especially Context) leaks
53 | --------
54 | Marking a non-primitive field as @Retained means it won't be released when a configuration change occures.
55 |
56 | Although Retainer makes sure at compile time that non of your @Retain fields is or is a subclass of Context, you should
57 | watch out for memory leaks and never retain an object holding a refernce to an android Context.
58 |
59 | When not to use
60 | --------
61 | Retainer is useful only in cases where your activty/fragment is recreated due to a configuration change.
62 |
63 | If you wish to retain fields for an activity/fragment which is recreated after being destroyed by the OS then you should use the plain old savedInstanceState.
64 |
65 | I recommend taking a look at [IcePick](https://github.com/frankiesardo/icepick)
66 |
67 | License
68 | -------
69 |
70 | Copyright 2017 Hanan Rofe Haim
71 |
72 | Licensed under the Apache License, Version 2.0 (the "License");
73 | you may not use this file except in compliance with the License.
74 | You may obtain a copy of the License at
75 |
76 | http://www.apache.org/licenses/LICENSE-2.0
77 |
78 | Unless required by applicable law or agreed to in writing, software
79 | distributed under the License is distributed on an "AS IS" BASIS,
80 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
81 | See the License for the specific language governing permissions and
82 | limitations under the License.
83 |
--------------------------------------------------------------------------------
/retainer-compiler/src/main/java/com/hananrh/retainer/compiler/Utils.java:
--------------------------------------------------------------------------------
1 | package com.hananrh.retainer.compiler;
2 |
3 | import com.squareup.javapoet.ClassName;
4 | import com.squareup.javapoet.JavaFile;
5 | import com.squareup.javapoet.TypeSpec;
6 |
7 | import java.io.IOException;
8 | import java.lang.annotation.Annotation;
9 | import java.util.ArrayList;
10 | import java.util.HashMap;
11 | import java.util.LinkedHashSet;
12 | import java.util.List;
13 | import java.util.Map;
14 | import java.util.Set;
15 |
16 | import javax.annotation.processing.ProcessingEnvironment;
17 | import javax.annotation.processing.RoundEnvironment;
18 | import javax.lang.model.element.Element;
19 | import javax.lang.model.element.TypeElement;
20 | import javax.lang.model.type.DeclaredType;
21 |
22 | /**
23 | * Created by Hanan on 2/13/2017.
24 | */
25 |
26 | public class Utils {
27 |
28 |
29 | static Set toAnnotationSet(Class... vars)
30 | {
31 | Set set = new LinkedHashSet<>();
32 | for (Class var : vars)
33 | {
34 | set.add(var.getCanonicalName());
35 | }
36 | return set;
37 | }
38 |
39 | static Map> mapElementsToClasses(Element[] elements)
40 | {
41 | Map> classNameToElements = new HashMap<>();
42 | for (Element element : elements)
43 | {
44 | String className = element.getEnclosingElement().getSimpleName().toString();
45 | List classElements = classNameToElements.get(className);
46 | if (classElements == null)
47 | {
48 | classElements = new ArrayList<>();
49 | classNameToElements.put(className, classElements);
50 | }
51 | classElements.add(element);
52 | }
53 | return classNameToElements;
54 | }
55 |
56 | static Element[] extractAnnotatedElements(RoundEnvironment roundEnv, Class extends Annotation> annotationClass)
57 | {
58 | Set extends Element> elementSet = roundEnv.getElementsAnnotatedWith(annotationClass);
59 | return elementSet.toArray(new Element[elementSet.size()]);
60 | }
61 |
62 | static String getPackage(ProcessingEnvironment pe, Element element)
63 | {
64 | return pe.getElementUtils().getPackageOf(element).getQualifiedName().toString();
65 | }
66 |
67 | static String getGeneratedClassName(String className, String suffix)
68 | {
69 | return className + suffix;
70 | }
71 |
72 | public static Element getSuperClassElement(Element classElement)
73 | {
74 | return ((DeclaredType) ((TypeElement) classElement).getSuperclass()).asElement();
75 | }
76 |
77 | static ClassName elementToClassName(ProcessingEnvironment pe, Element element)
78 | {
79 | return ClassName.get(getPackage(pe, element), element.getSimpleName().toString());
80 | }
81 |
82 | static ClassName elementToGenClassName(ProcessingEnvironment pe, Element element, String suffix)
83 | {
84 | return ClassName.get(getPackage(pe, element), getGeneratedClassName(element.getSimpleName().toString(), suffix));
85 | }
86 |
87 | static boolean writeClassToFile(ProcessingEnvironment env, String packageName, TypeSpec typeSpec)
88 | {
89 | try
90 | {
91 | JavaFile.builder(packageName, typeSpec).build().writeTo(env.getFiler());
92 | return true;
93 | }
94 | catch (IOException e)
95 | {
96 | return false;
97 | }
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/retainer/src/main/java/com/hananrh/retainer/Retainer.java:
--------------------------------------------------------------------------------
1 | package com.hananrh.retainer;
2 |
3 | import android.support.annotation.NonNull;
4 | import android.support.annotation.Nullable;
5 | import android.support.v4.app.Fragment;
6 | import android.support.v4.app.FragmentActivity;
7 | import android.support.v4.app.FragmentManager;
8 | import android.util.Log;
9 |
10 | import java.lang.reflect.Constructor;
11 | import java.lang.reflect.InvocationTargetException;
12 | import java.util.HashMap;
13 | import java.util.Map;
14 |
15 | /**
16 | * Created by Hanan on 2/7/2017.
17 | */
18 |
19 | public final class Retainer {
20 |
21 | private static final String LOG_TAG = "Retainer";
22 |
23 | private static final Map, IClassRetainer>> RETAINERS = new HashMap<>();
24 |
25 | private static boolean msLogsEnabled = false;
26 |
27 | private Retainer()
28 | {
29 | }
30 |
31 | public static void setLogsEnabled(boolean logsEnabled)
32 | {
33 | msLogsEnabled = logsEnabled;
34 | }
35 |
36 | public static void restore(@NonNull FragmentActivity target)
37 | {
38 | restore(target, target.getSupportFragmentManager());
39 | }
40 |
41 | public static void restore(@NonNull Fragment target)
42 | {
43 | restore(target, target.getFragmentManager());
44 | }
45 |
46 | public static void restore(@NonNull T target, @NonNull FragmentManager manager)
47 | {
48 | IClassRetainer retainerForClass = findOrCreateRetainerForClass(target);
49 | if (retainerForClass != null)
50 | {
51 | retainerForClass.restore(target, manager);
52 | }
53 | }
54 |
55 | public static void retain(@NonNull FragmentActivity target)
56 | {
57 | retain(target, target.getSupportFragmentManager());
58 | }
59 |
60 | public static void retain(@NonNull Fragment target)
61 | {
62 | retain(target, target.getFragmentManager());
63 | }
64 |
65 | public static void retain(@NonNull T target, @NonNull FragmentManager manager)
66 | {
67 | IClassRetainer retainerForClass = findOrCreateRetainerForClass(target);
68 | if (retainerForClass != null)
69 | {
70 | retainerForClass.retain(target, manager);
71 | }
72 | }
73 |
74 | @Nullable
75 | private static IClassRetainer findOrCreateRetainerForClass(@NonNull T target)
76 | {
77 | Class> targetClass = target.getClass();
78 | log("Looking for class constructor for: " + targetClass.getSimpleName());
79 | IClassRetainer retainer = findOrCreateRetainerForClass(targetClass, targetClass);
80 | if (retainer == null)
81 | {
82 | throw new RuntimeException("Unable to find retainer for " + targetClass.getSimpleName());
83 | }
84 | return retainer;
85 | }
86 |
87 | @SuppressWarnings("unchecked")
88 | @Nullable
89 | private static IClassRetainer findOrCreateRetainerForClass(Class> originClass, Class> cls)
90 | {
91 | IClassRetainer retainer = RETAINERS.get(cls);
92 | if (retainer != null)
93 | {
94 | log("Found cached retainer for: " + cls.getSimpleName());
95 | return retainer;
96 | }
97 | String clsName = cls.getName();
98 | if (clsName.startsWith("android.") || clsName.startsWith("java."))
99 | {
100 | log("Reached non-user class, stopping retainer search for: " + originClass.getSimpleName());
101 | return null;
102 | }
103 | try
104 | {
105 | Class> retainerClass = Class.forName(clsName + "Retainer");
106 | retainer = constructRetainer(retainerClass.getConstructor());
107 | log("Found and constructed retainer for: " + cls.getSimpleName());
108 | }
109 | catch (ClassNotFoundException e)
110 | {
111 | log("Retainer not found for: " + clsName + ", proceeding to superclass " + cls.getSuperclass().getName());
112 | retainer = findOrCreateRetainerForClass(originClass, cls.getSuperclass());
113 | }
114 | catch (NoSuchMethodException e)
115 | {
116 | throw new RuntimeException("Unable to find retainer constructor for " + clsName, e);
117 | }
118 | RETAINERS.put(cls, retainer);
119 | return retainer;
120 | }
121 |
122 | @SuppressWarnings("TryWithIdenticalCatches")
123 | private static IClassRetainer constructRetainer(Constructor> constructor)
124 | {
125 | try
126 | {
127 | return (IClassRetainer) constructor.newInstance();
128 | }
129 | catch (IllegalAccessException e)
130 | {
131 | throw new RuntimeException("Unable to invoke " + constructor, e);
132 | }
133 | catch (InstantiationException e)
134 | {
135 | throw new RuntimeException("Unable to invoke " + constructor, e);
136 | }
137 | catch (InvocationTargetException e)
138 | {
139 | Throwable cause = e.getCause();
140 | if (cause instanceof RuntimeException)
141 | {
142 | throw (RuntimeException) cause;
143 | }
144 | if (cause instanceof Error)
145 | {
146 | throw (Error) cause;
147 | }
148 | throw new RuntimeException("Unable to create retainer", cause);
149 | }
150 | }
151 |
152 | private static void log(String msg)
153 | {
154 | if (msLogsEnabled)
155 | {
156 | Log.d(LOG_TAG, msg);
157 | }
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/retainer-compiler/src/main/java/com/hananrh/retainer/compiler/RetainerProcessor.java:
--------------------------------------------------------------------------------
1 | package com.hananrh.retainer.compiler;
2 |
3 | import com.google.auto.service.AutoService;
4 | import com.hananrh.retainer.Retain;
5 | import com.hananrh.retainer.compiler.enforcers.AbsEnforcer;
6 | import com.hananrh.retainer.compiler.enforcers.ContextEnforcer;
7 | import com.hananrh.retainer.compiler.enforcers.EnclosingClassEnforcer;
8 | import com.hananrh.retainer.compiler.enforcers.ModifiersEnforcer;
9 | import com.squareup.javapoet.ClassName;
10 | import com.squareup.javapoet.MethodSpec;
11 | import com.squareup.javapoet.ParameterizedTypeName;
12 | import com.squareup.javapoet.TypeName;
13 | import com.squareup.javapoet.TypeSpec;
14 | import com.squareup.javapoet.TypeVariableName;
15 |
16 | import java.util.ArrayList;
17 | import java.util.List;
18 | import java.util.Map;
19 | import java.util.Set;
20 |
21 | import javax.annotation.processing.AbstractProcessor;
22 | import javax.annotation.processing.ProcessingEnvironment;
23 | import javax.annotation.processing.Processor;
24 | import javax.annotation.processing.RoundEnvironment;
25 | import javax.lang.model.SourceVersion;
26 | import javax.lang.model.element.Element;
27 | import javax.lang.model.element.Modifier;
28 | import javax.lang.model.element.TypeElement;
29 |
30 | import static com.hananrh.retainer.compiler.Utils.elementToClassName;
31 | import static com.hananrh.retainer.compiler.Utils.elementToGenClassName;
32 | import static com.hananrh.retainer.compiler.Utils.extractAnnotatedElements;
33 | import static com.hananrh.retainer.compiler.Utils.getSuperClassElement;
34 | import static com.hananrh.retainer.compiler.Utils.mapElementsToClasses;
35 | import static com.hananrh.retainer.compiler.Utils.toAnnotationSet;
36 | import static com.hananrh.retainer.compiler.Utils.writeClassToFile;
37 |
38 | @AutoService(Processor.class)
39 | public class RetainerProcessor extends AbstractProcessor {
40 |
41 | private static final String PACKAGE_NAME = "com.hananrh.retainer";
42 |
43 | private static final String GENERATED_CLASS_SUFFIX = "Retainer";
44 |
45 | //Methods
46 | private static final String METHOD_RESTORE = "restore";
47 | private static final String METHOD_RETAIN = "retain";
48 |
49 | //Types
50 | private static final ClassName TYPE_FRAGMENT_MANAGER = ClassName.get("android.support.v4.app", "FragmentManager");
51 | private static final ClassName TYPE_RETAINED_FIELDS_MAP_HOLDER = ClassName.get(PACKAGE_NAME, "RetainedFieldsMapHolder");
52 |
53 | //Parameters
54 | private static final String PAR_TARGET = "target";
55 | private static final String PAR_FRAGMENT_MANAGER = "manager";
56 |
57 | //Vars
58 | private static final String VAR_HOLDER = "holder";
59 |
60 |
61 | private Logger mLogger;
62 | private AbsEnforcer[] mEnforcers;
63 |
64 |
65 | @SuppressWarnings("unchecked")
66 | @Override
67 | public Set getSupportedAnnotationTypes()
68 | {
69 | return toAnnotationSet(Retain.class);
70 | }
71 |
72 | @Override
73 | public SourceVersion getSupportedSourceVersion()
74 | {
75 | return SourceVersion.latestSupported();
76 | }
77 |
78 | @Override
79 | public synchronized void init(ProcessingEnvironment processingEnvironment)
80 | {
81 | super.init(processingEnvironment);
82 | mLogger = new Logger(processingEnvironment);
83 | mEnforcers = new AbsEnforcer[]{
84 | new ModifiersEnforcer(mLogger),
85 | new EnclosingClassEnforcer(mLogger)
86 | ,new ContextEnforcer(mLogger)};
87 | }
88 |
89 | @Override
90 | public boolean process(Set extends TypeElement> annotations, RoundEnvironment roundEnv)
91 | {
92 | Element[] elements = extractAnnotatedElements(roundEnv, Retain.class);
93 | //Verify annotated elements are valid
94 | if (elements.length == 0 || !verifyElementsValid(elements))
95 | {
96 | return true;
97 | }
98 |
99 | Map> classNameToElements = mapElementsToClasses(elements);
100 | for (String className : classNameToElements.keySet())
101 | {
102 | //Generate retainer class for each class with retained elements
103 | List enclosedElements = classNameToElements.get(className);
104 | Element superClassElement = getSuperClassElement(enclosedElements.get(0).getEnclosingElement());
105 | boolean hasRetainerSuperClass = classNameToElements.containsKey(superClassElement.getSimpleName().toString());
106 | createRetainerForClass(hasRetainerSuperClass ? superClassElement : null, enclosedElements);
107 | }
108 | return true;
109 | }
110 |
111 | private boolean verifyElementsValid(Element[] elements)
112 | {
113 | for (AbsEnforcer enforcer : mEnforcers)
114 | {
115 | for (Element element : elements)
116 | {
117 | if (!enforcer.enforce(Retain.class, element))
118 | {
119 | return false;
120 | }
121 | }
122 | }
123 | return true;
124 | }
125 |
126 | private void createRetainerForClass(Element superClassElement, List enclosedElements)
127 | {
128 | Element classElement = enclosedElements.get(0).getEnclosingElement();
129 | ClassName className = elementToClassName(processingEnv, classElement);
130 |
131 | //Add methods
132 | List methodSpecList = new ArrayList<>();
133 | methodSpecList.add(restoreMethod(className, superClassElement != null, enclosedElements));
134 | methodSpecList.add(retainMethod(className, superClassElement != null, enclosedElements));
135 |
136 | //Generate class
137 | generateRetainerClass(className, classElement, superClassElement, methodSpecList);
138 | }
139 |
140 |
141 | private MethodSpec retainMethod(ClassName className, boolean hasSuper, List elements)
142 | {
143 | MethodSpec.Builder retainBuilder = retainerMethodBuilder(METHOD_RETAIN, hasSuper, className)
144 | .addStatement(String.format("RetainedFieldsMapHolder %s = " +
145 | "(RetainedFieldsMapHolder) %s.findFragmentByTag(%s.getClass().getName())",
146 | VAR_HOLDER, PAR_FRAGMENT_MANAGER, PAR_TARGET))
147 | .beginControlFlow(String.format("if (%s!=null)", VAR_HOLDER));
148 | for (Element element : elements)
149 | {
150 | retainBuilder.addStatement(String.format("%s.getMap().put(\"%s\", target.%s)",
151 | VAR_HOLDER, element.getSimpleName(), element.getSimpleName()));
152 | }
153 | retainBuilder.endControlFlow();
154 | return retainBuilder.build();
155 | }
156 |
157 | private MethodSpec restoreMethod(ClassName className, boolean hasSuper, List elements)
158 | {
159 | MethodSpec.Builder retainBuilder = retainerMethodBuilder(METHOD_RESTORE, hasSuper, className)
160 | .addStatement(String.format("RetainedFieldsMapHolder %s = " +
161 | "($T) %s.findFragmentByTag(%s.getClass().getName())", VAR_HOLDER, PAR_FRAGMENT_MANAGER, PAR_TARGET), TYPE_RETAINED_FIELDS_MAP_HOLDER)
162 | .beginControlFlow(String.format("if (%s == null)", VAR_HOLDER))
163 | .addCode("//Nothing to restore, just add holder for next time\n")
164 | .addStatement(String.format("%s = new RetainedFieldsMapHolder()", VAR_HOLDER))
165 | .addStatement(String.format("%s.beginTransaction().add(%s, target.getClass().getName()).commitNow()", PAR_FRAGMENT_MANAGER, VAR_HOLDER))
166 | .endControlFlow()
167 | .beginControlFlow(String.format("else if (!%s.getMap().isEmpty())", VAR_HOLDER))
168 | .addCode("//Restore all fields from mapping\n");
169 | for (Element element : elements)
170 | {
171 | retainBuilder.addStatement(String.format("%s.%s = ($T) %s.getMap().get(\"%s\")",
172 | PAR_TARGET, element.getSimpleName(), VAR_HOLDER, element.getSimpleName()), element.asType());
173 | }
174 | retainBuilder.endControlFlow();
175 | return retainBuilder.build();
176 | }
177 |
178 | private MethodSpec.Builder retainerMethodBuilder(String name, boolean hasSuper, ClassName className)
179 | {
180 | MethodSpec.Builder builder = MethodSpec.methodBuilder(name)
181 | .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
182 | .addAnnotation(Override.class)
183 | .returns(TypeName.VOID)
184 | .addParameter(className, PAR_TARGET)
185 | .addParameter(TYPE_FRAGMENT_MANAGER, PAR_FRAGMENT_MANAGER);
186 | if (hasSuper)
187 | {
188 | builder.addStatement(String.format("super.%s(%s,%s)", name, PAR_TARGET, PAR_FRAGMENT_MANAGER));
189 | }
190 | return builder;
191 | }
192 |
193 | private void generateRetainerClass(ClassName className, Element classElement,
194 | Element superClassElement, List methodSpecs)
195 | {
196 | TypeSpec.Builder typeSpecBuilder = TypeSpec.classBuilder(getGeneratedClassName(
197 | className.simpleName()))
198 | .addModifiers(Modifier.PUBLIC);
199 | //Add generic type
200 | String genericType = "T";
201 | typeSpecBuilder.addTypeVariable(TypeVariableName.get(genericType, className));
202 |
203 | //Define inheritance, if target don't have a retained super class just implement
204 | //interface, otherwise extend superclass retainer
205 | if (superClassElement != null)
206 | {
207 | typeSpecBuilder.superclass(ParameterizedTypeName.get(
208 | elementToGenClassName(processingEnv, superClassElement, GENERATED_CLASS_SUFFIX),
209 | TypeVariableName.get(genericType)));
210 | }
211 | else
212 | {
213 | typeSpecBuilder.addSuperinterface(ParameterizedTypeName.get(
214 | ClassName.get(PACKAGE_NAME, "IClassRetainer"), TypeVariableName.get(genericType)));
215 | }
216 |
217 | //Add methods
218 | typeSpecBuilder.addMethods(methodSpecs).build();
219 |
220 | //Write to java generated source file
221 | if (!writeClassToFile(processingEnv, className.packageName(), typeSpecBuilder.build()))
222 | {
223 | mLogger.e(classElement, "Unable to write retainer for class: %s", classElement.getSimpleName().toString());
224 | }
225 | }
226 |
227 | private String getGeneratedClassName(String forClass)
228 | {
229 | return Utils.getGeneratedClassName(forClass, GENERATED_CLASS_SUFFIX);
230 | }
231 |
232 |
233 | }
234 |
--------------------------------------------------------------------------------