
In this article I will talk about how I solved the problems that I encountered in the previous part during the implementation of the project .
 Firstly, when analyzing a transformable class, you need to somehow understand whether this class is the successor of the Activity
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     or Fragment
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     , in order to say with confidence that the class is suitable for our transformation. 
  Secondly, in the transformed .class
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     file for all fields with the @State
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     annotation, @State
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     need to explicitly determine the type in order to call the corresponding method on the bundle for saving / restoring the state, and you can determine the type exactly by analyzing all the parents of the class and the interfaces they implement. 
Thus, you just need to be able to analyze the abstract syntax tree of the transformed files.
  In order to analyze the class for inheritance from some base class (in our case, it is Activity/Fragment
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     ), it is enough to have the full path to the .class
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     file under study.  Further, it all depends on the implementation of the transformer: either load the class through ClassLoader
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     , or analyze through ASM using ClassReader
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     and ClassVisitor
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     , getting all the necessary information about the class. 
  Keep in mind that the class we need can be located outside the project scope, and in some library (for example, Activity
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     is in the Android SDK).  Therefore, before starting the transformation, you need to get a list of paths to all available .class
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     files. 
To do this, make small changes to the Transformer :
 @Override Set<? super QualifiedContent.Scope> getReferencedScopes() { return ImmutableSet.of( QualifiedContent.Scope.EXTERNAL_LIBRARIES, QualifiedContent.Scope.SUB_PROJECTS ) }
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
        The getReferencedScopes
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     method allows you to access files from the specified scopes, and this will simply be read access without the possibility of transformation.  Just what we need.  In the transform
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     method, these files can be obtained in much the same way as from the main scopes: 
 transformInvocation.referencedInputs.each { transformInput -> transformInput.directoryInputs.each { directoryInput -> // .  directoryInput.file.absolutePath } transformInput.jarInputs.each { jarInput -> // .  jarInput.file.absolutePath } }
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
      And one more thing, files from the Andoid SDK need to be received separately:
 project.extensions.findByType(BaseExtension.class).bootClasspath[0].toString()
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
        Thanks Google, very convenient. 
  Filling the list of all .class
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     files available to us with your hands is rather dreary: since we get directories or jar
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     files as an input, you need to go around all of them and get the .class
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     files correctly.  Here, I used the previously mentioned javassist library.  She does it all under the hood and plus has a convenient api for working with the classes received.  In the end, you just need to transfer the path to the files and fill in ClassPool
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     : 
 ClassPool.getDefault().appendClassPath("  ")
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
        Before starting the transformation, ClassPool
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     from all possible file sources: 
fillPoolAndroidInputs(classPool) fillPoolReferencedInputs(transformInvocation, classPool) fillPoolInputs(transformInvocation, classPool)
Details in the transformer .
  Now that the ClassPool
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     full, it remains to get rid of the @Stater
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     annotation.  To do this, remove the check in the visitAnnotation
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     method of our visitor and simply examine the superclass of each class for the presence of Activity/Fragment
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     in the inheritance hierarchy.  Getting any class by name from the javassist pool class is very simple: 
 CtClass currentClass = ClassPool.getDefault().get(className.replace("/", "."))
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
        And already with CtClass
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     you can get currentClass.superclass
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     or currentClass.interfaces
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     .  Through comparison of the superclass, I did an activity / fragment check. 
  And finally, to get rid of StateType
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     and not specify the type of field to save explicitly, I did about the same.  For convenience, a mapper (with tests ) was written that parses the current descriptor into the type supported by the bundle. 
As a result, the code transformation has not changed; only the mechanism for determining the type of a variable has changed.
  So, combining 2 approaches to working with .class
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     files, I managed to implement the original idea of ββsaving variables in a bundle using just one annotation. 
  This time, to test the performance, I connected the plug-in to a real working project, since the filling of the pool class depends on the number of files in the project and various libraries. 
      
        
        
        
      
      Checked all this through ./gradlew clean build --scan
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     .  The transformation transformClassesWithStaterTransformForDebug
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     takes approximately 2.5 s.  I measured with one Activity
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     with 50 @State
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     fields and with 10 such Activity
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     , the speed does not change much.