Adam Lesinski | 282e181 | 2014-01-23 18:17:42 -0800 | [diff] [blame] | 1 | # Copyright (C) 2008 The Android Open Source Project |
| 2 | |
| 3 | |
| 4 | - Description - |
| 5 | --------------- |
| 6 | |
Deepanshu Gupta | 88585f4 | 2014-04-08 16:16:43 -0700 | [diff] [blame] | 7 | Layoutlib_create generates a JAR library used by the Eclipse graphical layout editor to perform |
| 8 | layout. |
Adam Lesinski | 282e181 | 2014-01-23 18:17:42 -0800 | [diff] [blame] | 9 | |
| 10 | |
| 11 | - Usage - |
| 12 | --------- |
| 13 | |
Deepanshu Gupta | 88585f4 | 2014-04-08 16:16:43 -0700 | [diff] [blame] | 14 | ./layoutlib_create destination.jar path/to/android1.jar path/to/android2.jar |
Adam Lesinski | 282e181 | 2014-01-23 18:17:42 -0800 | [diff] [blame] | 15 | |
| 16 | |
| 17 | - Design Overview - |
| 18 | ------------------- |
| 19 | |
Deepanshu Gupta | 88585f4 | 2014-04-08 16:16:43 -0700 | [diff] [blame] | 20 | Layoutlib_create uses a few jars from the framework containing the Java code used by Android as |
| 21 | generated by the Android build, right before the classes are converted to a DEX format. |
Adam Lesinski | 282e181 | 2014-01-23 18:17:42 -0800 | [diff] [blame] | 22 | |
Deepanshu Gupta | 88585f4 | 2014-04-08 16:16:43 -0700 | [diff] [blame] | 23 | These jars can't be used directly in Eclipse as: |
| 24 | - they contains references to native code (which we want to avoid in Eclipse), |
| 25 | - some classes need to be overridden, for example all the drawing code that is replaced by Java 2D |
| 26 | calls in Eclipse. |
| 27 | - some of the classes that need to be changed are final and/or we need access to their private |
| 28 | internal state. |
Adam Lesinski | 282e181 | 2014-01-23 18:17:42 -0800 | [diff] [blame] | 29 | |
| 30 | Consequently this tool: |
| 31 | - parses the input JAR, |
| 32 | - modifies some of the classes directly using some bytecode manipulation, |
| 33 | - filters some packages and removes those we don't want in the output JAR, |
| 34 | - injects some new classes, |
Deepanshu Gupta | 88585f4 | 2014-04-08 16:16:43 -0700 | [diff] [blame] | 35 | - generates a modified JAR file that is suitable for the Android plugin for Eclipse to perform |
| 36 | rendering. |
Adam Lesinski | 282e181 | 2014-01-23 18:17:42 -0800 | [diff] [blame] | 37 | |
| 38 | The ASM library is used to do the bytecode modification using its visitor pattern API. |
| 39 | |
Deepanshu Gupta | 88585f4 | 2014-04-08 16:16:43 -0700 | [diff] [blame] | 40 | The layoutlib_create is *NOT* generic. There is no configuration file. Instead all the configuration |
| 41 | is done in the main() method and the CreateInfo structure is expected to change with the Android |
| 42 | platform as new classes are added, changed or removed. |
Adam Lesinski | 282e181 | 2014-01-23 18:17:42 -0800 | [diff] [blame] | 43 | |
Deepanshu Gupta | 88585f4 | 2014-04-08 16:16:43 -0700 | [diff] [blame] | 44 | The resulting JAR is used by layoutlib_bridge (a.k.a. "the bridge"), also part of the platform, that |
| 45 | provides all the necessary missing implementation for rendering graphics in Eclipse. |
Adam Lesinski | 282e181 | 2014-01-23 18:17:42 -0800 | [diff] [blame] | 46 | |
| 47 | |
| 48 | |
| 49 | - Implementation Notes - |
| 50 | ------------------------ |
| 51 | |
| 52 | The tool works in two phases: |
| 53 | - first analyze the input jar (AsmAnalyzer class) |
| 54 | - then generate the output jar (AsmGenerator class), |
| 55 | |
| 56 | |
| 57 | - Analyzer |
| 58 | ---------- |
| 59 | |
Deepanshu Gupta | 88585f4 | 2014-04-08 16:16:43 -0700 | [diff] [blame] | 60 | The goal of the analyzer is to create a graph of all the classes from the input JAR with their |
| 61 | dependencies and then only keep the ones we want. |
Adam Lesinski | 282e181 | 2014-01-23 18:17:42 -0800 | [diff] [blame] | 62 | |
Deepanshu Gupta | 88585f4 | 2014-04-08 16:16:43 -0700 | [diff] [blame] | 63 | To do that, the analyzer is created with a list of base classes to keep -- everything that derives |
| 64 | from these is kept. Currently the one such class is android.view.View: since we want to render |
| 65 | layouts, anything that is sort of a view needs to be kept. |
Adam Lesinski | 282e181 | 2014-01-23 18:17:42 -0800 | [diff] [blame] | 66 | |
Deepanshu Gupta | 88585f4 | 2014-04-08 16:16:43 -0700 | [diff] [blame] | 67 | The analyzer is also given a list of class names to keep in the output. This is done using |
| 68 | shell-like glob patterns that filter on the fully-qualified class names, for example "android.*.R**" |
| 69 | ("*" does not matches dots whilst "**" does, and "." and "$" are interpreted as-is). In practice we |
| 70 | almost but not quite request the inclusion of full packages. |
Adam Lesinski | 282e181 | 2014-01-23 18:17:42 -0800 | [diff] [blame] | 71 | |
Deepanshu Gupta | 88585f4 | 2014-04-08 16:16:43 -0700 | [diff] [blame] | 72 | The analyzer is also given a list of classes to exclude. A fake implementation of these classes is |
| 73 | injected by the Generator. |
Deepanshu Gupta | 03a057c | 2013-11-06 15:15:32 +0530 | [diff] [blame] | 74 | |
Deepanshu Gupta | 88585f4 | 2014-04-08 16:16:43 -0700 | [diff] [blame] | 75 | With this information, the analyzer parses the input zip to find all the classes. All classes |
| 76 | deriving from the requested bases classes are kept. All classes whose name match the glob pattern |
| 77 | are kept. The analysis then finds all the dependencies of the classes that are to be kept using an |
| 78 | ASM visitor on the class, the field types, the method types and annotations types. Classes that |
| 79 | belong to the current JRE are excluded. |
Adam Lesinski | 282e181 | 2014-01-23 18:17:42 -0800 | [diff] [blame] | 80 | |
Deepanshu Gupta | 88585f4 | 2014-04-08 16:16:43 -0700 | [diff] [blame] | 81 | The output of the analyzer is a set of ASM ClassReader instances which are then fed to the |
| 82 | generator. |
Adam Lesinski | 282e181 | 2014-01-23 18:17:42 -0800 | [diff] [blame] | 83 | |
| 84 | |
| 85 | - Generator |
| 86 | ----------- |
| 87 | |
Deepanshu Gupta | 88585f4 | 2014-04-08 16:16:43 -0700 | [diff] [blame] | 88 | The generator is constructed from a CreateInfo struct that acts as a config file and lists: |
| 89 | - the classes to inject in the output JAR -- these classes are directly implemented in |
| 90 | layoutlib_create and will be used to interface with the renderer in Eclipse. |
Adam Lesinski | 282e181 | 2014-01-23 18:17:42 -0800 | [diff] [blame] | 91 | - specific methods to override (see method stubs details below). |
| 92 | - specific methods for which to delegate calls. |
| 93 | - specific methods to remove based on their return type. |
| 94 | - specific classes to rename. |
Deepanshu Gupta | 03a057c | 2013-11-06 15:15:32 +0530 | [diff] [blame] | 95 | - specific classes to refactor. |
Adam Lesinski | 282e181 | 2014-01-23 18:17:42 -0800 | [diff] [blame] | 96 | |
Deepanshu Gupta | 88585f4 | 2014-04-08 16:16:43 -0700 | [diff] [blame] | 97 | Each of these are specific strategies we use to be able to modify the Android code to fit within the |
| 98 | Eclipse renderer. These strategies are explained beow. |
Adam Lesinski | 282e181 | 2014-01-23 18:17:42 -0800 | [diff] [blame] | 99 | |
Deepanshu Gupta | 88585f4 | 2014-04-08 16:16:43 -0700 | [diff] [blame] | 100 | The core method of the generator is transform(): it takes an input ASM ClassReader and modifies it |
| 101 | to produce a byte array suitable for the final JAR file. |
Adam Lesinski | 282e181 | 2014-01-23 18:17:42 -0800 | [diff] [blame] | 102 | |
Deepanshu Gupta | 03a057c | 2013-11-06 15:15:32 +0530 | [diff] [blame] | 103 | The first step of the transformation is to implement the method delegates. |
Adam Lesinski | 282e181 | 2014-01-23 18:17:42 -0800 | [diff] [blame] | 104 | |
Deepanshu Gupta | 88585f4 | 2014-04-08 16:16:43 -0700 | [diff] [blame] | 105 | The TransformClassAdapter is then used to process the potentially renamed class. All protected or |
| 106 | private classes are market as public. All classes are made non-final. Interfaces are left as-is. |
Adam Lesinski | 282e181 | 2014-01-23 18:17:42 -0800 | [diff] [blame] | 107 | |
Deepanshu Gupta | 88585f4 | 2014-04-08 16:16:43 -0700 | [diff] [blame] | 108 | If a method has a return type that must be erased, the whole method is skipped. Methods are also |
| 109 | changed from protected/private to public. The code of the methods is then kept as-is, except for |
| 110 | native methods which are replaced by a stub. Methods that are to be overridden are also replaced by |
| 111 | a stub. |
Adam Lesinski | 282e181 | 2014-01-23 18:17:42 -0800 | [diff] [blame] | 112 | |
Adam Lesinski | 282e181 | 2014-01-23 18:17:42 -0800 | [diff] [blame] | 113 | Finally fields are also visited and changed from protected/private to public. |
| 114 | |
Deepanshu Gupta | 88585f4 | 2014-04-08 16:16:43 -0700 | [diff] [blame] | 115 | The next step of the transformation is changing the name of the class in case we requested the class |
| 116 | to be renamed. This uses the RenameClassAdapter to also rename all inner classes and references in |
| 117 | methods and types. Note that other classes are not transformed and keep referencing the original |
| 118 | name. |
Deepanshu Gupta | 03a057c | 2013-11-06 15:15:32 +0530 | [diff] [blame] | 119 | |
Deepanshu Gupta | 88585f4 | 2014-04-08 16:16:43 -0700 | [diff] [blame] | 120 | The class is then fed to RefactorClassAdapter which is like RenameClassAdapter but updates the |
| 121 | references in all classes. This is used to update the references of classes in the java package that |
Deepanshu Gupta | 1160e6d | 2014-06-09 18:57:18 -0700 | [diff] [blame] | 122 | were added in the Dalvik VM but are not a part of the Desktop VM. The existing classes are |
| 123 | modified to update all references to these non-desktop classes. An alternate implementation of |
Deepanshu Gupta | 88585f4 | 2014-04-08 16:16:43 -0700 | [diff] [blame] | 124 | these (com.android.tools.layoutlib.java.*) is injected. |
Deepanshu Gupta | 03a057c | 2013-11-06 15:15:32 +0530 | [diff] [blame] | 125 | |
Deepanshu Gupta | 88585f4 | 2014-04-08 16:16:43 -0700 | [diff] [blame] | 126 | RenameClassAdapter and RefactorClassAdapter both inherit from AbstractClassAdapter which changes the |
| 127 | class version (version of the JDK used to compile the class) to 50 (corresponding to Java 6), if the |
| 128 | class was originally compiled with Java 7 (version 51). This is because we don't currently generate |
| 129 | the StackMapTable correctly and Java 7 VM enforces that classes with version greater than 51 have |
| 130 | valid StackMapTable. As a side benefit of this, we can continue to support Java 6 because Java 7 on |
| 131 | Mac has horrible font rendering support. |
| 132 | |
Deepanshu Gupta | 1160e6d | 2014-06-09 18:57:18 -0700 | [diff] [blame] | 133 | ReplaceMethodCallsAdapter replaces calls to certain methods. Currently, it only rewrites calls to |
Deepanshu Gupta | e1960cc | 2014-07-10 13:20:42 -0700 | [diff] [blame^] | 134 | specialized versions of java.lang.System.arraycopy(), which are not part of the Desktop VM to call |
| 135 | the more general method java.lang.System.arraycopy(Ljava/lang/Object;ILjava/lang/Object;II)V. |
Deepanshu Gupta | 1160e6d | 2014-06-09 18:57:18 -0700 | [diff] [blame] | 136 | |
Deepanshu Gupta | 88585f4 | 2014-04-08 16:16:43 -0700 | [diff] [blame] | 137 | The ClassAdapters are chained together to achieve the desired output. (Look at section 2.2.7 |
| 138 | Transformation chains in the asm user guide, link in the References.) The order of execution of |
| 139 | these is: |
Deepanshu Gupta | 03a057c | 2013-11-06 15:15:32 +0530 | [diff] [blame] | 140 | ClassReader -> [DelegateClassAdapter] -> TransformClassAdapter -> [RenameClassAdapter] -> |
Deepanshu Gupta | 1160e6d | 2014-06-09 18:57:18 -0700 | [diff] [blame] | 141 | RefactorClassAdapter -> [ReplaceMethodCallsAdapter] -> ClassWriter |
Adam Lesinski | 282e181 | 2014-01-23 18:17:42 -0800 | [diff] [blame] | 142 | |
| 143 | - Method stubs |
| 144 | -------------- |
| 145 | |
Deepanshu Gupta | 88585f4 | 2014-04-08 16:16:43 -0700 | [diff] [blame] | 146 | As indicated above, all native and overridden methods are replaced by a stub. We don't have the |
| 147 | code to replace with in layoutlib_create. Instead the StubMethodAdapter replaces the code of the |
| 148 | method by a call to OverrideMethod.invokeX(). When using the final JAR, the bridge can register |
Adam Lesinski | 282e181 | 2014-01-23 18:17:42 -0800 | [diff] [blame] | 149 | listeners from these overridden method calls based on the method signatures. |
| 150 | |
Deepanshu Gupta | 88585f4 | 2014-04-08 16:16:43 -0700 | [diff] [blame] | 151 | The listeners are currently pretty basic: we only pass the signature of the method being called, its |
| 152 | caller object and a flag indicating whether the method was native. We do not currently provide the |
| 153 | parameters. The listener can however specify the return value of the overridden method. |
Adam Lesinski | 282e181 | 2014-01-23 18:17:42 -0800 | [diff] [blame] | 154 | |
| 155 | This strategy is now obsolete and replaced by the method delegates. |
| 156 | |
| 157 | |
| 158 | - Strategies |
| 159 | ------------ |
| 160 | |
Deepanshu Gupta | 88585f4 | 2014-04-08 16:16:43 -0700 | [diff] [blame] | 161 | We currently have 6 strategies to deal with overriding the rendering code and make it run in |
| 162 | Eclipse. Most of these strategies are implemented hand-in-hand by the bridge (which runs in Eclipse) |
| 163 | and the generator. |
Adam Lesinski | 282e181 | 2014-01-23 18:17:42 -0800 | [diff] [blame] | 164 | |
| 165 | |
| 166 | 1- Class Injection |
| 167 | |
Deepanshu Gupta | 03a057c | 2013-11-06 15:15:32 +0530 | [diff] [blame] | 168 | This is the easiest: we currently inject the following classes: |
Deepanshu Gupta | 88585f4 | 2014-04-08 16:16:43 -0700 | [diff] [blame] | 169 | - OverrideMethod and its associated MethodListener and MethodAdapter are used to intercept calls to |
| 170 | some specific methods that are stubbed out and change their return value. |
| 171 | - CreateInfo class, which configured the generator. Not used yet, but could in theory help us track |
| 172 | what the generator changed. |
| 173 | - AutoCloseable and Objects are part of Java 7. To enable us to still run on Java 6, new classes are |
| 174 | injected. The implementation for these classes has been taken from Android's libcore |
| 175 | (platform/libcore/luni/src/main/java/java/...). |
Deepanshu Gupta | 1160e6d | 2014-06-09 18:57:18 -0700 | [diff] [blame] | 176 | - Charsets, IntegralToString and UnsafeByteSequence are not part of the Desktop VM. They are |
Deepanshu Gupta | 88585f4 | 2014-04-08 16:16:43 -0700 | [diff] [blame] | 177 | added to the Dalvik VM for performance reasons. An implementation that is very close to the |
| 178 | original (which is at platform/libcore/luni/src/main/java/...) is injected. Since these classees |
| 179 | were in part of the java package, where we can't inject classes, all references to these have been |
| 180 | updated (See strategy 4- Refactoring Classes). |
Adam Lesinski | 282e181 | 2014-01-23 18:17:42 -0800 | [diff] [blame] | 181 | |
| 182 | |
| 183 | 2- Overriding methods |
| 184 | |
Deepanshu Gupta | 88585f4 | 2014-04-08 16:16:43 -0700 | [diff] [blame] | 185 | As explained earlier, the creator doesn't have any replacement code for methods to override. Instead |
| 186 | it removes the original code and replaces it by a call to a specific OveriddeMethod.invokeX(). The |
| 187 | bridge then registers a listener on the method signature and can provide an implementation. |
Adam Lesinski | 282e181 | 2014-01-23 18:17:42 -0800 | [diff] [blame] | 188 | |
Deepanshu Gupta | 88585f4 | 2014-04-08 16:16:43 -0700 | [diff] [blame] | 189 | This strategy is now obsolete and replaced by the method delegates (See strategy 6- Method |
| 190 | Delegates). |
Adam Lesinski | 282e181 | 2014-01-23 18:17:42 -0800 | [diff] [blame] | 191 | |
| 192 | |
| 193 | 3- Renaming classes |
| 194 | |
Deepanshu Gupta | 88585f4 | 2014-04-08 16:16:43 -0700 | [diff] [blame] | 195 | This simply changes the name of a class in its definition, as well as all its references in internal |
| 196 | inner classes and methods. Calls from other classes are not modified -- they keep referencing the |
| 197 | original class name. This allows the bridge to literally replace an implementation. |
Adam Lesinski | 282e181 | 2014-01-23 18:17:42 -0800 | [diff] [blame] | 198 | |
Deepanshu Gupta | 88585f4 | 2014-04-08 16:16:43 -0700 | [diff] [blame] | 199 | An example will make this easier: android.graphics.Paint is the main drawing class that we need to |
| 200 | replace. To do so, the generator renames Paint to _original_Paint. Later the bridge provides its own |
| 201 | replacement version of Paint which will be used by the rest of the Android stack. The replacement |
| 202 | version of Paint can still use (either by inheritance or delegation) all the original non-native |
| 203 | code of _original_Paint if it so desires. |
Adam Lesinski | 282e181 | 2014-01-23 18:17:42 -0800 | [diff] [blame] | 204 | |
Deepanshu Gupta | 88585f4 | 2014-04-08 16:16:43 -0700 | [diff] [blame] | 205 | Some of the Android classes are basically wrappers over native objects and since we don't have the |
| 206 | native code in Eclipse, we need to provide a full alternate implementation. Sub-classing doesn't |
| 207 | work as some native methods are static and we don't control object creation. |
Adam Lesinski | 282e181 | 2014-01-23 18:17:42 -0800 | [diff] [blame] | 208 | |
| 209 | This won't rename/replace the inner static methods of a given class. |
| 210 | |
| 211 | |
Deepanshu Gupta | 03a057c | 2013-11-06 15:15:32 +0530 | [diff] [blame] | 212 | 4- Refactoring classes |
| 213 | |
Deepanshu Gupta | 88585f4 | 2014-04-08 16:16:43 -0700 | [diff] [blame] | 214 | This is very similar to the Renaming classes except that it also updates the reference in all |
| 215 | classes. This is done for classes which are added to the Dalvik VM for performance reasons but are |
Deepanshu Gupta | 1160e6d | 2014-06-09 18:57:18 -0700 | [diff] [blame] | 216 | not present in the Desktop VM. An implementation for these classes is also injected. |
Deepanshu Gupta | 03a057c | 2013-11-06 15:15:32 +0530 | [diff] [blame] | 217 | |
| 218 | |
| 219 | 5- Method erasure based on return type |
Adam Lesinski | 282e181 | 2014-01-23 18:17:42 -0800 | [diff] [blame] | 220 | |
Deepanshu Gupta | 88585f4 | 2014-04-08 16:16:43 -0700 | [diff] [blame] | 221 | This is mostly an implementation detail of the bridge: in the Paint class mentioned above, some |
| 222 | inner static classes are used to pass around attributes (e.g. FontMetrics, or the Style enum) and |
| 223 | all the original implementation is native. |
Adam Lesinski | 282e181 | 2014-01-23 18:17:42 -0800 | [diff] [blame] | 224 | |
Deepanshu Gupta | 88585f4 | 2014-04-08 16:16:43 -0700 | [diff] [blame] | 225 | In this case we have a strategy that tells the generator that anything returning, for example, the |
| 226 | inner class Paint$Style in the Paint class should be discarded and the bridge will provide its own |
| 227 | implementation. |
Adam Lesinski | 282e181 | 2014-01-23 18:17:42 -0800 | [diff] [blame] | 228 | |
| 229 | |
Deepanshu Gupta | 03a057c | 2013-11-06 15:15:32 +0530 | [diff] [blame] | 230 | 6- Method Delegates |
Adam Lesinski | 282e181 | 2014-01-23 18:17:42 -0800 | [diff] [blame] | 231 | |
Deepanshu Gupta | 88585f4 | 2014-04-08 16:16:43 -0700 | [diff] [blame] | 232 | This strategy is used to override method implementations. Given a method SomeClass.MethodName(), 1 |
| 233 | or 2 methods are generated: |
| 234 | a- A copy of the original method named SomeClass.MethodName_Original(). The content is the original |
| 235 | method as-is from the reader. This step is omitted if the method is native, since it has no Java |
| 236 | implementation. |
| 237 | b- A brand new implementation of SomeClass.MethodName() which calls to a non-existing static method |
| 238 | named SomeClass_Delegate.MethodName(). The implementation of this 'delegate' method is done in |
| 239 | layoutlib_brigde. |
Adam Lesinski | 282e181 | 2014-01-23 18:17:42 -0800 | [diff] [blame] | 240 | |
Deepanshu Gupta | 88585f4 | 2014-04-08 16:16:43 -0700 | [diff] [blame] | 241 | The delegate method is a static method. If the original method is non-static, the delegate method |
| 242 | receives the original 'this' as its first argument. If the original method is an inner non-static |
| 243 | method, it also receives the inner 'this' as the second argument. |
Adam Lesinski | 282e181 | 2014-01-23 18:17:42 -0800 | [diff] [blame] | 244 | |
| 245 | |
| 246 | |
| 247 | - References - |
| 248 | -------------- |
| 249 | |
| 250 | |
| 251 | The JVM Specification 2nd edition: |
| 252 | http://java.sun.com/docs/books/jvms/second_edition/html/VMSpecTOC.doc.html |
| 253 | |
| 254 | Understanding bytecode: |
| 255 | http://www.ibm.com/developerworks/ibm/library/it-haggar_bytecode/ |
| 256 | |
| 257 | Bytecode opcode list: |
| 258 | http://en.wikipedia.org/wiki/Java_bytecode_instruction_listings |
| 259 | |
| 260 | ASM user guide: |
Deepanshu Gupta | 03a057c | 2013-11-06 15:15:32 +0530 | [diff] [blame] | 261 | http://download.forge.objectweb.org/asm/asm4-guide.pdf |
Adam Lesinski | 282e181 | 2014-01-23 18:17:42 -0800 | [diff] [blame] | 262 | |
| 263 | |
| 264 | -- |
| 265 | end |