diff options
Diffstat (limited to 'tools')
14 files changed, 376 insertions, 146 deletions
diff --git a/tools/aapt/AaptAssets.cpp b/tools/aapt/AaptAssets.cpp index e4f447e..2b2ec7b 100644 --- a/tools/aapt/AaptAssets.cpp +++ b/tools/aapt/AaptAssets.cpp @@ -685,13 +685,11 @@ bool AaptGroupEntry::getMncName(const char* name, if (*c != 0) return false; if (c-val == 0 || c-val > 3) return false; - int d = atoi(val); - if (d != 0) { - if (out) out->mnc = d; - return true; + if (out) { + out->mnc = atoi(val); } - return false; + return true; } /* diff --git a/tools/aapt/Bundle.h b/tools/aapt/Bundle.h index c5aa573..fa84e93 100644 --- a/tools/aapt/Bundle.h +++ b/tools/aapt/Bundle.h @@ -40,12 +40,13 @@ public: mWantUTF16(false), mValues(false), mCompressionMethod(0), mOutputAPKFile(NULL), mManifestPackageNameOverride(NULL), mInstrumentationPackageNameOverride(NULL), + mIsOverlayPackage(false), mAutoAddOverlay(false), mAssetSourceDir(NULL), mProguardFile(NULL), mAndroidManifestFile(NULL), mPublicOutputFile(NULL), mRClassDir(NULL), mResourceIntermediatesDir(NULL), mManifestMinSdkVersion(NULL), mMinSdkVersion(NULL), mTargetSdkVersion(NULL), mMaxSdkVersion(NULL), mVersionCode(NULL), mVersionName(NULL), mCustomPackage(NULL), - mMaxResVersion(NULL), mDebugMode(false), mProduct(NULL), + mMaxResVersion(NULL), mDebugMode(false), mNonConstantId(false), mProduct(NULL), mArgc(0), mArgv(NULL) {} ~Bundle(void) {} @@ -92,6 +93,8 @@ public: void setManifestPackageNameOverride(const char * val) { mManifestPackageNameOverride = val; } const char* getInstrumentationPackageNameOverride() const { return mInstrumentationPackageNameOverride; } void setInstrumentationPackageNameOverride(const char * val) { mInstrumentationPackageNameOverride = val; } + bool getIsOverlayPackage() const { return mIsOverlayPackage; } + void setIsOverlayPackage(bool val) { mIsOverlayPackage = val; } bool getAutoAddOverlay() { return mAutoAddOverlay; } void setAutoAddOverlay(bool val) { mAutoAddOverlay = val; } @@ -139,6 +142,8 @@ public: void setMaxResVersion(const char * val) { mMaxResVersion = val; } bool getDebugMode() { return mDebugMode; } void setDebugMode(bool val) { mDebugMode = val; } + bool getNonConstantId() { return mNonConstantId; } + void setNonConstantId(bool val) { mNonConstantId = val; } const char* getProduct() const { return mProduct; } void setProduct(const char * val) { mProduct = val; } @@ -217,6 +222,7 @@ private: const char* mOutputAPKFile; const char* mManifestPackageNameOverride; const char* mInstrumentationPackageNameOverride; + bool mIsOverlayPackage; bool mAutoAddOverlay; const char* mAssetSourceDir; const char* mProguardFile; @@ -239,6 +245,7 @@ private: const char* mCustomPackage; const char* mMaxResVersion; bool mDebugMode; + bool mNonConstantId; const char* mProduct; /* file specification */ diff --git a/tools/aapt/Main.cpp b/tools/aapt/Main.cpp index 739b01f..1e63131 100644 --- a/tools/aapt/Main.cpp +++ b/tools/aapt/Main.cpp @@ -68,6 +68,7 @@ void usage(void) " [-S resource-sources [-S resource-sources ...]] " " [-F apk-file] [-J R-file-dir] \\\n" " [--product product1,product2,...] \\\n" + " [-o] \\\n" " [raw-files-dir [raw-files-dir] ...]\n" "\n" " Package the android resources. It will read assets and resources that are\n" @@ -105,6 +106,7 @@ void usage(void) " -j specify a jar or zip file containing classes to include\n" " -k junk path of file(s) added\n" " -m make package directories under location specified by -J\n" + " -o create overlay package (ie only resources; expects <overlay-package> in manifest)\n" #if 0 " -p pseudolocalize the default configuration\n" #endif @@ -160,7 +162,11 @@ void usage(void) " product variants\n" " --utf16\n" " changes default encoding for resources to UTF-16. Only useful when API\n" - " level is set to 7 or higher where the default encoding is UTF-8.\n"); + " level is set to 7 or higher where the default encoding is UTF-8.\n" + " --non-constant-id\n" + " Make the resources ID non constant. This is required to make an R java class\n" + " that does not contain the final value but is used to make reusable compiled\n" + " libraries that need to access resources.\n"); } /* @@ -271,6 +277,9 @@ int main(int argc, char* const argv[]) case 'm': bundle.setMakePackageDirs(true); break; + case 'o': + bundle.setIsOverlayPackage(true); + break; #if 0 case 'p': bundle.setPseudolocalize(true); @@ -497,6 +506,8 @@ int main(int argc, char* const argv[]) goto bail; } bundle.setProduct(argv[0]); + } else if (strcmp(cp, "-non-constant-id") == 0) { + bundle.setNonConstantId(true); } else { fprintf(stderr, "ERROR: Unknown option '-%s'\n", cp); wantUsage = true; diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp index c8ba904..0a4f24f 100644 --- a/tools/aapt/Resource.cpp +++ b/tools/aapt/Resource.cpp @@ -1655,7 +1655,8 @@ static status_t writeLayoutClasses( static status_t writeSymbolClass( FILE* fp, const sp<AaptAssets>& assets, bool includePrivate, - const sp<AaptSymbols>& symbols, const String8& className, int indent) + const sp<AaptSymbols>& symbols, const String8& className, int indent, + bool nonConstantId) { fprintf(fp, "%spublic %sfinal class %s {\n", getIndentSpace(indent), @@ -1665,6 +1666,10 @@ static status_t writeSymbolClass( size_t i; status_t err = NO_ERROR; + const char * id_format = nonConstantId ? + "%spublic static int %s=0x%08x;\n" : + "%spublic static final int %s=0x%08x;\n"; + size_t N = symbols->getSymbols().size(); for (i=0; i<N; i++) { const AaptSymbolEntry& sym = symbols->getSymbols().valueAt(i); @@ -1717,7 +1722,7 @@ static status_t writeSymbolClass( if (deprecated) { fprintf(fp, "%s@Deprecated\n", getIndentSpace(indent)); } - fprintf(fp, "%spublic static final int %s=0x%08x;\n", + fprintf(fp, id_format, getIndentSpace(indent), String8(name).string(), (int)sym.int32Val); } @@ -1768,7 +1773,7 @@ static status_t writeSymbolClass( if (nclassName == "styleable") { styleableSymbols = nsymbols; } else { - err = writeSymbolClass(fp, assets, includePrivate, nsymbols, nclassName, indent); + err = writeSymbolClass(fp, assets, includePrivate, nsymbols, nclassName, indent, nonConstantId); } if (err != NO_ERROR) { return err; @@ -1839,7 +1844,7 @@ status_t writeResourceSymbols(Bundle* bundle, const sp<AaptAssets>& assets, "\n" "package %s;\n\n", package.string()); - status_t err = writeSymbolClass(fp, assets, includePrivate, symbols, className, 0); + status_t err = writeSymbolClass(fp, assets, includePrivate, symbols, className, 0, bundle->getNonConstantId()); if (err != NO_ERROR) { return err; } diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp index 29644a6..6459d0e 100644 --- a/tools/aapt/ResourceTable.cpp +++ b/tools/aapt/ResourceTable.cpp @@ -1322,6 +1322,22 @@ status_t compileResourceFile(Bundle* bundle, } } } else if (strcmp16(block.getElementName(&len), string_array16.string()) == 0) { + // Check whether these strings need valid formats. + // (simplified form of what string16 does above) + size_t n = block.getAttributeCount(); + for (size_t i = 0; i < n; i++) { + size_t length; + const uint16_t* attr = block.getAttributeName(i, &length); + if (strcmp16(attr, translatable16.string()) == 0 + || strcmp16(attr, formatted16.string()) == 0) { + const uint16_t* value = block.getAttributeStringValue(i, &length); + if (strcmp16(value, false16.string()) == 0) { + curIsFormatted = false; + break; + } + } + } + curTag = &string_array16; curType = array16; curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_STRING; @@ -1724,13 +1740,6 @@ status_t ResourceTable::startBag(const SourcePos& sourcePos, // If a parent is explicitly specified, set it. if (bagParent.size() > 0) { - String16 curPar = e->getParent(); - if (curPar.size() > 0 && curPar != bagParent) { - sourcePos.error("Conflicting parents specified, was '%s', now '%s'\n", - String8(e->getParent()).string(), - String8(bagParent).string()); - return UNKNOWN_ERROR; - } e->setParent(bagParent); } @@ -1778,13 +1787,6 @@ status_t ResourceTable::addBag(const SourcePos& sourcePos, // If a parent is explicitly specified, set it. if (bagParent.size() > 0) { - String16 curPar = e->getParent(); - if (curPar.size() > 0 && curPar != bagParent) { - sourcePos.error("Conflicting parents specified, was '%s', now '%s'\n", - String8(e->getParent()).string(), - String8(bagParent).string()); - return UNKNOWN_ERROR; - } e->setParent(bagParent); } @@ -3672,7 +3674,9 @@ sp<ResourceTable::Package> ResourceTable::getPackage(const String16& package) { sp<Package> p = mPackages.valueFor(package); if (p == NULL) { - if (mIsAppPackage) { + if (mBundle->getIsOverlayPackage()) { + p = new Package(package, 0x00); + } else if (mIsAppPackage) { if (mHaveAppPackage) { fprintf(stderr, "Adding multiple application package resources; only one is allowed.\n" "Use -x to create extended resources.\n"); diff --git a/tools/aapt/XMLNode.cpp b/tools/aapt/XMLNode.cpp index 8551b0f..19248bf 100644 --- a/tools/aapt/XMLNode.cpp +++ b/tools/aapt/XMLNode.cpp @@ -451,13 +451,15 @@ void printXMLBlock(ResXMLTree* block) printf("=?0x%x", (int)value.data); } else if (value.dataType == Res_value::TYPE_STRING) { printf("=\"%s\"", - String8(block->getAttributeStringValue(i, &len)).string()); + ResTable::normalizeForOutput(String8(block->getAttributeStringValue(i, + &len)).string()).string()); } else { printf("=(type 0x%x)0x%x", (int)value.dataType, (int)value.data); } const char16_t* val = block->getAttributeStringValue(i, &len); if (val != NULL) { - printf(" (Raw: \"%s\")", String8(val).string()); + printf(" (Raw: \"%s\")", ResTable::normalizeForOutput(String8(val).string()). + string()); } printf("\n"); } @@ -502,7 +504,8 @@ void printXMLBlock(ResXMLTree* block) namespaces.pop(); } else if (code == ResXMLTree::TEXT) { size_t len; - printf("%sC: \"%s\"\n", prefix.string(), String8(block->getText(&len)).string()); + printf("%sC: \"%s\"\n", prefix.string(), ResTable::normalizeForOutput( + String8(block->getText(&len)).string()).string()); } } diff --git a/tools/layoutlib/create/README.txt b/tools/layoutlib/create/README.txt index 65a64cd..894611b 100644 --- a/tools/layoutlib/create/README.txt +++ b/tools/layoutlib/create/README.txt @@ -30,9 +30,9 @@ The Android JAR can't be used directly in Eclipse: Consequently this tool: - parses the input JAR, - modifies some of the classes directly using some bytecode manipulation, -- filters some packages and removes some that we don't want to end in the output JAR, +- filters some packages and removes those we don't want in the output JAR, - injects some new classes, -- and generates a modified JAR file that is suitable for the Android plugin +- generates a modified JAR file that is suitable for the Android plugin for Eclipse to perform rendering. The ASM library is used to do the bytecode modification using its visitor pattern API. @@ -63,7 +63,7 @@ with their dependencies and then only keep the ones we want. To do that, the analyzer is created with a list of base classes to keep -- everything that derives from these is kept. Currently the one such class is android.view.View: -since we want to render layouts, anything that is sort of the view needs to be kept. +since we want to render layouts, anything that is sort of a view needs to be kept. The analyzer is also given a list of class names to keep in the output. This is done using shell-like glob patterns that filter on the fully-qualified @@ -90,6 +90,7 @@ and lists: - the classes to inject in the output JAR -- these classes are directly implemented in layoutlib_create and will be used to interface with the renderer in Eclipse. - specific methods to override (see method stubs details below). +- specific methods for which to delegate calls. - specific methods to remove based on their return type. - specific classes to rename. @@ -114,6 +115,9 @@ Methods are also changed from protected/private to public. The code of the methods is then kept as-is, except for native methods which are replaced by a stub. Methods that are to be overridden are also replaced by a stub. +The transformed class is then fed through the DelegateClassAdapter to implement +method delegates. + Finally fields are also visited and changed from protected/private to public. @@ -131,8 +135,7 @@ method being called, its caller object and a flag indicating whether the method was native. We do not currently provide the parameters. The listener can however specify the return value of the overridden method. -An extension being worked on is to actually replace these listeners by -direct calls to a delegate class, complete with parameters. +This strategy is now obsolete and replaced by the method delegates. - Strategies @@ -160,6 +163,9 @@ methods to override. Instead it removes the original code and replaces it by a call to a specific OveriddeMethod.invokeX(). The bridge then registers a listener on the method signature and can provide an implementation. +This strategy is now obsolete and replaced by the method delegates. +See strategy 5 below. + 3- Renaming classes @@ -195,6 +201,24 @@ example, the inner class Paint$Style in the Paint class should be discarded and bridge will provide its own implementation. +5- Method Delegates + +This strategy is used to override method implementations. +Given a method SomeClass.MethodName(), 1 or 2 methods are generated: +a- A copy of the original method named SomeClass.MethodName_Original(). + The content is the original method as-is from the reader. + This step is omitted if the method is native, since it has no Java implementation. +b- A brand new implementation of SomeClass.MethodName() which calls to a + non-existing static method named SomeClass_Delegate.MethodName(). + The implementation of this 'delegate' method is done in layoutlib_brigde. + +The delegate method is a static method. +If the original method is non-static, the delegate method receives the original 'this' +as its first argument. If the original method is an inner non-static method, it also +receives the inner 'this' as the second argument. + + + - References - -------------- diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java index 8602782..95506c6 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java @@ -51,6 +51,8 @@ public final class CreateInfo implements ICreateInfo { * Returns The list of methods to stub out. Each entry must be in the form * "package.package.OuterClass$InnerClass#MethodName". * The list can be empty but must not be null. + * <p/> + * This usage is deprecated. Please use method 'delegates' instead. */ public String[] getOverriddenMethods() { return OVERRIDDEN_METHODS; @@ -153,6 +155,7 @@ public final class CreateInfo implements ICreateInfo { /** * The list of methods to stub out. Each entry must be in the form * "package.package.OuterClass$InnerClass#MethodName". + * This usage is deprecated. Please use method 'delegates' instead. */ private final static String[] OVERRIDDEN_METHODS = new String[] { }; diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java index 9cba8a0..49ddf1d 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java @@ -31,6 +31,11 @@ import java.util.Set; */ public class DelegateClassAdapter extends ClassAdapter { + /** Suffix added to original methods. */ + private static final String ORIGINAL_SUFFIX = "_Original"; + private static String CONSTRUCTOR = "<init>"; + private static String CLASS_INIT = "<clinit>"; + public final static String ALL_NATIVES = "<<all_natives>>"; private final String mClassName; @@ -73,22 +78,55 @@ public class DelegateClassAdapter extends ClassAdapter { boolean useDelegate = (isNative && mDelegateMethods.contains(ALL_NATIVES)) || mDelegateMethods.contains(name); - if (useDelegate) { - // remove native - access = access & ~Opcodes.ACC_NATIVE; + if (!useDelegate) { + // Not creating a delegate for this method, pass it as-is from the reader + // to the writer. + return super.visitMethod(access, name, desc, signature, exceptions); } - MethodVisitor mw = super.visitMethod(access, name, desc, signature, exceptions); if (useDelegate) { - DelegateMethodAdapter a = new DelegateMethodAdapter(mLog, mw, mClassName, - name, desc, isStatic); - if (isNative) { - // A native has no code to visit, so we need to generate it directly. - a.generateCode(); - } else { - return a; + if (CONSTRUCTOR.equals(name) || CLASS_INIT.equals(name)) { + // We don't currently support generating delegates for constructors. + throw new UnsupportedOperationException( + String.format( + "Delegate doesn't support overriding constructor %1$s:%2$s(%3$s)", //$NON-NLS-1$ + mClassName, name, desc)); } } - return mw; + + if (isNative) { + // Remove native flag + access = access & ~Opcodes.ACC_NATIVE; + MethodVisitor mwDelegate = super.visitMethod(access, name, desc, signature, exceptions); + + DelegateMethodAdapter2 a = new DelegateMethodAdapter2( + mLog, null /*mwOriginal*/, mwDelegate, mClassName, name, desc, isStatic); + + // A native has no code to visit, so we need to generate it directly. + a.generateDelegateCode(); + + return mwDelegate; + } + + // Given a non-native SomeClass.MethodName(), we want to generate 2 methods: + // - A copy of the original method named SomeClass.MethodName_Original(). + // The content is the original method as-is from the reader. + // - A brand new implementation of SomeClass.MethodName() which calls to a + // non-existing method named SomeClass_Delegate.MethodName(). + // The implementation of this 'delegate' method is done in layoutlib_brigde. + + int accessDelegate = access; + // change access to public for the original one + access &= ~(Opcodes.ACC_PROTECTED | Opcodes.ACC_PRIVATE); + access |= Opcodes.ACC_PUBLIC; + + MethodVisitor mwOriginal = super.visitMethod(access, name + ORIGINAL_SUFFIX, + desc, signature, exceptions); + MethodVisitor mwDelegate = super.visitMethod(accessDelegate, name, + desc, signature, exceptions); + + DelegateMethodAdapter2 a = new DelegateMethodAdapter2( + mLog, mwOriginal, mwDelegate, mClassName, name, desc, isStatic); + return a; } } diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter2.java index 8d7f016..ac4ae6d 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter2.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008 The Android Open Source Project + * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,36 +30,57 @@ import org.objectweb.asm.Type; import java.util.ArrayList; /** - * This method adapter rewrites a method by discarding the original code and generating - * a call to a delegate. Original annotations are passed along unchanged. + * This method adapter generates delegate methods. * <p/> - * Calls are delegated to a class named <code><className>_Delegate</code> with - * static methods matching the methods to be overridden here. The methods have the - * same return type. The argument type list is the same except the "this" reference is - * passed first for non-static methods. + * Given a method {@code SomeClass.MethodName()}, this generates 1 or 2 methods: + * <ul> + * <li> A copy of the original method named {@code SomeClass.MethodName_Original()}. + * The content is the original method as-is from the reader. + * This step is omitted if the method is native, since it has no Java implementation. + * <li> A brand new implementation of {@code SomeClass.MethodName()} which calls to a + * non-existing method named {@code SomeClass_Delegate.MethodName()}. + * The implementation of this 'delegate' method is done in layoutlib_brigde. + * </ul> + * A method visitor is generally constructed to generate a single method; however + * here we might want to generate one or two depending on the context. To achieve + * that, the visitor here generates the 'original' method and acts as a no-op if + * no such method exists (e.g. when the original is a native method). + * The delegate method is generated after the {@code visitEnd} of the original method + * or by having the class adapter <em>directly</em> call {@link #generateDelegateCode()} + * for native methods. * <p/> - * A new annotation is added. + * When generating the 'delegate', the implementation generates a call to a class + * class named <code><className>_Delegate</code> with static methods matching + * the methods to be overridden here. The methods have the same return type. + * The argument type list is the same except the "this" reference is passed first + * for non-static methods. * <p/> - * Note that native methods have, by definition, no code so there's nothing a visitor - * can visit. That means the caller must call {@link #generateCode()} directly for + * A new annotation is added to these 'delegate' methods so that we can easily find them + * for automated testing. + * <p/> + * This class isn't intended to be generic or reusable. + * It is called by {@link DelegateClassAdapter}, which takes care of properly initializing + * the two method writers for the original and the delegate class, as needed, with their + * expected names. + * <p/> + * The class adapter also takes care of calling {@link #generateDelegateCode()} directly for * a native and use the visitor pattern for non-natives. + * Note that native methods have, by definition, no code so there's nothing a visitor + * can visit. * <p/> - * Instances of this class are not re-usable. You need a new instance for each method. + * Instances of this class are not re-usable. + * The class adapter creates a new instance for each method. */ -class DelegateMethodAdapter implements MethodVisitor { +class DelegateMethodAdapter2 implements MethodVisitor { - /** - * Suffix added to delegate classes. - */ + /** Suffix added to delegate classes. */ public static final String DELEGATE_SUFFIX = "_Delegate"; - private static String CONSTRUCTOR = "<init>"; - private static String CLASS_INIT = "<clinit>"; - - /** The parent method writer */ - private MethodVisitor mParentVisitor; - /** Flag to output the first line number. */ - private boolean mOutputFirstLineNumber = true; + /** The parent method writer to copy of the original method. + * Null when dealing with a native original method. */ + private MethodVisitor mOrgWriter; + /** The parent method writer to generate the delegating method. Never null. */ + private MethodVisitor mDelWriter; /** The original method descriptor (return type + argument types.) */ private String mDesc; /** True if the original method is static. */ @@ -70,17 +91,22 @@ class DelegateMethodAdapter implements MethodVisitor { private final String mMethodName; /** Logger object. */ private final Log mLog; - /** True if {@link #visitCode()} has been invoked. */ - private boolean mVisitCodeCalled; + + /** Array used to capture the first line number information from the original method + * and duplicate it in the delegate. */ + private Object[] mDelegateLineNumber; /** - * Creates a new {@link DelegateMethodAdapter} that will transform this method + * Creates a new {@link DelegateMethodAdapter2} that will transform this method * into a delegate call. * <p/> - * See {@link DelegateMethodAdapter} for more details. + * See {@link DelegateMethodAdapter2} for more details. * * @param log The logger object. Must not be null. - * @param mv the method visitor to which this adapter must delegate calls. + * @param mvOriginal The parent method writer to copy of the original method. + * Must be {@code null} when dealing with a native original method. + * @param mvDelegate The parent method writer to generate the delegating method. + * Must never be null. * @param className The internal class name of the class to visit, * e.g. <code>com/android/SomeClass$InnerClass</code>. * @param methodName The simple name of the method. @@ -88,28 +114,20 @@ class DelegateMethodAdapter implements MethodVisitor { * {@link Type#getArgumentTypes(String)}) * @param isStatic True if the method is declared static. */ - public DelegateMethodAdapter(Log log, - MethodVisitor mv, + public DelegateMethodAdapter2(Log log, + MethodVisitor mvOriginal, + MethodVisitor mvDelegate, String className, String methodName, String desc, boolean isStatic) { mLog = log; - mParentVisitor = mv; + mOrgWriter = mvOriginal; + mDelWriter = mvDelegate; mClassName = className; mMethodName = methodName; mDesc = desc; mIsStatic = isStatic; - - if (CONSTRUCTOR.equals(methodName) || CLASS_INIT.equals(methodName)) { - // We're going to simplify by not supporting constructors. - // The only trick with a constructor is to find the proper super constructor - // and call it (and deciding if we should mirror the original method call to - // a custom constructor or call a default one.) - throw new UnsupportedOperationException( - String.format("Delegate doesn't support overriding constructor %1$s:%2$s(%3$s)", - className, methodName, desc)); - } } /** @@ -119,25 +137,25 @@ class DelegateMethodAdapter implements MethodVisitor { * (since they have no code to visit). * <p/> * Otherwise for non-native methods the {@link DelegateClassAdapter} simply needs to - * return this instance of {@link DelegateMethodAdapter} and let the normal visitor pattern + * return this instance of {@link DelegateMethodAdapter2} and let the normal visitor pattern * invoke it as part of the {@link ClassReader#accept(ClassVisitor, int)} workflow and then * this method will be invoked from {@link MethodVisitor#visitEnd()}. */ - public void generateCode() { + public void generateDelegateCode() { /* * The goal is to generate a call to a static delegate method. * If this method is non-static, the first parameter will be 'this'. * All the parameters must be passed and then the eventual return type returned. * * Example, let's say we have a method such as - * public void method_1(int a, Object b, ArrayList<String> c) { ... } + * public void myMethod(int a, Object b, ArrayList<String> c) { ... } * * We'll want to create a body that calls a delegate method like this: - * TheClass_Delegate.method_1(this, a, b, c); + * TheClass_Delegate.myMethod(this, a, b, c); * * If the method is non-static and the class name is an inner class (e.g. has $ in its * last segment), we want to push the 'this' of the outer class first: - * OuterClass_InnerClass_Delegate.method_1( + * OuterClass_InnerClass_Delegate.myMethod( * OuterClass.this, * OuterClass$InnerClass.this, * a, b, c); @@ -147,20 +165,22 @@ class DelegateMethodAdapter implements MethodVisitor { * * The generated class name is the current class name with "_Delegate" appended to it. * One thing to realize is that we don't care about generics -- since generic types - * are erased at runtime, they have no influence on the method name being called. + * are erased at build time, they have no influence on the method name being called. */ // Add our annotation - AnnotationVisitor aw = mParentVisitor.visitAnnotation( + AnnotationVisitor aw = mDelWriter.visitAnnotation( Type.getObjectType(Type.getInternalName(LayoutlibDelegate.class)).toString(), true); // visible at runtime - aw.visitEnd(); + if (aw != null) { + aw.visitEnd(); + } + + mDelWriter.visitCode(); - if (!mVisitCodeCalled) { - // If this is a direct call to generateCode() as done by DelegateClassAdapter - // for natives, visitCode() hasn't been called yet. - mParentVisitor.visitCode(); - mVisitCodeCalled = true; + if (mDelegateLineNumber != null) { + Object[] p = mDelegateLineNumber; + mDelWriter.visitLineNumber((Integer) p[0], (Label) p[1]); } ArrayList<Type> paramTypes = new ArrayList<Type>(); @@ -186,8 +206,8 @@ class DelegateMethodAdapter implements MethodVisitor { // that points to the outer class. // Push this.getField("this$0") on the call stack. - mParentVisitor.visitVarInsn(Opcodes.ALOAD, 0); // var 0 = this - mParentVisitor.visitFieldInsn(Opcodes.GETFIELD, + mDelWriter.visitVarInsn(Opcodes.ALOAD, 0); // var 0 = this + mDelWriter.visitFieldInsn(Opcodes.GETFIELD, mClassName, // class where the field is defined "this$0", // field name outerType.getDescriptor()); // type of the field @@ -196,7 +216,7 @@ class DelegateMethodAdapter implements MethodVisitor { } // Push "this" for the instance method, which is always ALOAD 0 - mParentVisitor.visitVarInsn(Opcodes.ALOAD, 0); + mDelWriter.visitVarInsn(Opcodes.ALOAD, 0); maxStack++; pushedArg0 = true; paramTypes.add(Type.getObjectType(mClassName)); @@ -207,7 +227,7 @@ class DelegateMethodAdapter implements MethodVisitor { int maxLocals = pushedArg0 ? 1 : 0; for (Type t : argTypes) { int size = t.getSize(); - mParentVisitor.visitVarInsn(t.getOpcode(Opcodes.ILOAD), maxLocals); + mDelWriter.visitVarInsn(t.getOpcode(Opcodes.ILOAD), maxLocals); maxLocals += size; maxStack += size; paramTypes.add(t); @@ -220,16 +240,16 @@ class DelegateMethodAdapter implements MethodVisitor { paramTypes.toArray(new Type[paramTypes.size()])); // Invoke the static delegate - mParentVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, + mDelWriter.visitMethodInsn(Opcodes.INVOKESTATIC, delegateClassName, mMethodName, desc); Type returnType = Type.getReturnType(mDesc); - mParentVisitor.visitInsn(returnType.getOpcode(Opcodes.IRETURN)); + mDelWriter.visitInsn(returnType.getOpcode(Opcodes.IRETURN)); - mParentVisitor.visitMaxs(maxStack, maxLocals); - mParentVisitor.visitEnd(); + mDelWriter.visitMaxs(maxStack, maxLocals); + mDelWriter.visitEnd(); // For debugging now. Maybe we should collect these and store them in // a text file for helping create the delegates. We could also compare @@ -241,42 +261,60 @@ class DelegateMethodAdapter implements MethodVisitor { /* Pass down to visitor writer. In this implementation, either do nothing. */ public void visitCode() { - mVisitCodeCalled = true; - mParentVisitor.visitCode(); + if (mOrgWriter != null) { + mOrgWriter.visitCode(); + } } /* * visitMaxs is called just before visitEnd if there was any code to rewrite. - * Skip the original. */ public void visitMaxs(int maxStack, int maxLocals) { + if (mOrgWriter != null) { + mOrgWriter.visitMaxs(maxStack, maxLocals); + } } - /** - * End of visiting. Generate the messaging code. - */ + /** End of visiting. Generate the delegating code. */ public void visitEnd() { - generateCode(); + if (mOrgWriter != null) { + mOrgWriter.visitEnd(); + } + generateDelegateCode(); } /* Writes all annotation from the original method. */ public AnnotationVisitor visitAnnotation(String desc, boolean visible) { - return mParentVisitor.visitAnnotation(desc, visible); + if (mOrgWriter != null) { + return mOrgWriter.visitAnnotation(desc, visible); + } else { + return null; + } } /* Writes all annotation default values from the original method. */ public AnnotationVisitor visitAnnotationDefault() { - return mParentVisitor.visitAnnotationDefault(); + if (mOrgWriter != null) { + return mOrgWriter.visitAnnotationDefault(); + } else { + return null; + } } public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) { - return mParentVisitor.visitParameterAnnotation(parameter, desc, visible); + if (mOrgWriter != null) { + return mOrgWriter.visitParameterAnnotation(parameter, desc, visible); + } else { + return null; + } } /* Writes all attributes from the original method. */ public void visitAttribute(Attribute attr) { - mParentVisitor.visitAttribute(attr); + if (mOrgWriter != null) { + mOrgWriter.visitAttribute(attr); + } } /* @@ -284,75 +322,110 @@ class DelegateMethodAdapter implements MethodVisitor { * viewers can direct to the correct method, even if the content doesn't match. */ public void visitLineNumber(int line, Label start) { - if (mOutputFirstLineNumber) { - mParentVisitor.visitLineNumber(line, start); - mOutputFirstLineNumber = false; + // Capture the first line values for the new delegate method + if (mDelegateLineNumber == null) { + mDelegateLineNumber = new Object[] { line, start }; + } + if (mOrgWriter != null) { + mOrgWriter.visitLineNumber(line, start); } } public void visitInsn(int opcode) { - // Skip original code. + if (mOrgWriter != null) { + mOrgWriter.visitInsn(opcode); + } } public void visitLabel(Label label) { - // Skip original code. + if (mOrgWriter != null) { + mOrgWriter.visitLabel(label); + } } public void visitTryCatchBlock(Label start, Label end, Label handler, String type) { - // Skip original code. + if (mOrgWriter != null) { + mOrgWriter.visitTryCatchBlock(start, end, handler, type); + } } public void visitMethodInsn(int opcode, String owner, String name, String desc) { - // Skip original code. + if (mOrgWriter != null) { + mOrgWriter.visitMethodInsn(opcode, owner, name, desc); + } } public void visitFieldInsn(int opcode, String owner, String name, String desc) { - // Skip original code. + if (mOrgWriter != null) { + mOrgWriter.visitFieldInsn(opcode, owner, name, desc); + } } public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) { - // Skip original code. + if (mOrgWriter != null) { + mOrgWriter.visitFrame(type, nLocal, local, nStack, stack); + } } public void visitIincInsn(int var, int increment) { - // Skip original code. + if (mOrgWriter != null) { + mOrgWriter.visitIincInsn(var, increment); + } } public void visitIntInsn(int opcode, int operand) { - // Skip original code. + if (mOrgWriter != null) { + mOrgWriter.visitIntInsn(opcode, operand); + } } public void visitJumpInsn(int opcode, Label label) { - // Skip original code. + if (mOrgWriter != null) { + mOrgWriter.visitJumpInsn(opcode, label); + } } public void visitLdcInsn(Object cst) { - // Skip original code. + if (mOrgWriter != null) { + mOrgWriter.visitLdcInsn(cst); + } } public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) { - // Skip original code. + if (mOrgWriter != null) { + mOrgWriter.visitLocalVariable(name, desc, signature, start, end, index); + } } public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) { - // Skip original code. + if (mOrgWriter != null) { + mOrgWriter.visitLookupSwitchInsn(dflt, keys, labels); + } } public void visitMultiANewArrayInsn(String desc, int dims) { - // Skip original code. + if (mOrgWriter != null) { + mOrgWriter.visitMultiANewArrayInsn(desc, dims); + } } public void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels) { - // Skip original code. + if (mOrgWriter != null) { + mOrgWriter.visitTableSwitchInsn(min, max, dflt, labels); + } } public void visitTypeInsn(int opcode, String type) { - // Skip original code. + if (mOrgWriter != null) { + mOrgWriter.visitTypeInsn(opcode, type); + } } public void visitVarInsn(int opcode, int var) { - // Skip original code. + if (mOrgWriter != null) { + mOrgWriter.visitVarInsn(opcode, var); + } } } diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/StubMethodAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/StubMethodAdapter.java index 9a57a4a..d70d028 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/StubMethodAdapter.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/StubMethodAdapter.java @@ -31,7 +31,7 @@ class StubMethodAdapter implements MethodVisitor { private static String CONSTRUCTOR = "<init>"; private static String CLASS_INIT = "<clinit>"; - + /** The parent method writer */ private MethodVisitor mParentVisitor; /** The method return type. Can be null. */ @@ -40,7 +40,7 @@ class StubMethodAdapter implements MethodVisitor { private String mInvokeSignature; /** Flag to output the first line number. */ private boolean mOutputFirstLineNumber = true; - /** Flag that is true when implementing a constructor, to accept all original + /** Flag that is true when implementing a constructor, to accept all original * code calling the original super constructor. */ private boolean mIsInitMethod = false; @@ -55,12 +55,12 @@ class StubMethodAdapter implements MethodVisitor { mInvokeSignature = invokeSignature; mIsStatic = isStatic; mIsNative = isNative; - + if (CONSTRUCTOR.equals(methodName) || CLASS_INIT.equals(methodName)) { mIsInitMethod = true; } } - + private void generateInvoke() { /* Generates the code: * OverrideMethod.invoke("signature", mIsNative ? true : false, null or this); @@ -188,7 +188,7 @@ class StubMethodAdapter implements MethodVisitor { } mParentVisitor.visitMaxs(maxStack, maxLocals); } - + /** * End of visiting. * For non-constructor, generate the messaging code and the return statement @@ -250,6 +250,7 @@ class StubMethodAdapter implements MethodVisitor { generatePop(); generateInvoke(); mMessageGenerated = true; + //$FALL-THROUGH$ default: mParentVisitor.visitInsn(opcode); } @@ -346,5 +347,5 @@ class StubMethodAdapter implements MethodVisitor { mParentVisitor.visitVarInsn(opcode, var); } } - + } diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java index e8b3ea8..6e120ce 100644 --- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java +++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java @@ -130,7 +130,7 @@ public class DelegateClassAdapterTest { } /** - * {@link DelegateMethodAdapter} does not support overriding constructors yet, + * {@link DelegateMethodAdapter2} does not support overriding constructors yet, * so this should fail with an {@link UnsupportedOperationException}. * * Although not tested here, the message of the exception should contain the @@ -202,6 +202,7 @@ public class DelegateClassAdapterTest { // We'll delegate the "get" method of both the inner and outer class. HashSet<String> delegateMethods = new HashSet<String>(); delegateMethods.add("get"); + delegateMethods.add("privateMethod"); // Generate the delegate for the outer class. ClassWriter cwOuter = new ClassWriter(0 /*flags*/); @@ -234,6 +235,25 @@ public class DelegateClassAdapterTest { // The original Outer.get returns 1+10+20, // but the delegate makes it return 4+10+20 assertEquals(4+10+20, callGet(o2, 10, 20)); + assertEquals(1+10+20, callGet_Original(o2, 10, 20)); + + // The original Outer has a private method that is + // delegated. We should be able to call both the delegate + // and the original (which is now public). + assertEquals("outerPrivateMethod", + callMethod(o2, "privateMethod_Original", false /*makePublic*/)); + + // The original method is private, so by default we can't access it + boolean gotIllegalAccessException = false; + try { + callMethod(o2, "privateMethod", false /*makePublic*/); + } catch(IllegalAccessException e) { + gotIllegalAccessException = true; + } + assertTrue(gotIllegalAccessException); + // Try again, but now making it accessible + assertEquals("outerPrivate_Delegate", + callMethod(o2, "privateMethod", true /*makePublic*/)); // Check the inner class. Since it's not a static inner class, we need // to use the hidden constructor that takes the outer class as first parameter. @@ -246,6 +266,7 @@ public class DelegateClassAdapterTest { // The original Inner.get returns 3+10+20, // but the delegate makes it return 6+10+20 assertEquals(6+10+20, callGet(i2, 10, 20)); + assertEquals(3+10+20, callGet_Original(i2, 10, 20)); } }; cl2.add(OUTER_CLASS_NAME, cwOuter.toByteArray()); @@ -319,7 +340,7 @@ public class DelegateClassAdapterTest { } /** - * Accesses {@link OuterClass#get()} or {@link InnerClass#get() }via reflection. + * Accesses {@link OuterClass#get} or {@link InnerClass#get}via reflection. */ public int callGet(Object instance, int a, long b) throws Exception { Method m = instance.getClass().getMethod("get", @@ -330,6 +351,39 @@ public class DelegateClassAdapterTest { } /** + * Accesses the "_Original" methods for {@link OuterClass#get} + * or {@link InnerClass#get}via reflection. + */ + public int callGet_Original(Object instance, int a, long b) throws Exception { + Method m = instance.getClass().getMethod("get_Original", + new Class<?>[] { int.class, long.class } ); + + Object result = m.invoke(instance, new Object[] { a, b }); + return ((Integer) result).intValue(); + } + + /** + * Accesses the any declared method that takes no parameter via reflection. + */ + @SuppressWarnings("unchecked") + public <T> T callMethod(Object instance, String methodName, boolean makePublic) throws Exception { + Method m = instance.getClass().getDeclaredMethod(methodName, (Class<?>[])null); + + boolean wasAccessible = m.isAccessible(); + if (makePublic && !wasAccessible) { + m.setAccessible(true); + } + + Object result = m.invoke(instance, (Object[])null); + + if (makePublic && !wasAccessible) { + m.setAccessible(false); + } + + return (T) result; + } + + /** * Accesses {@link ClassWithNative#add(int, int)} via reflection. */ public int callAdd(Object instance, int a, int b) throws Exception { diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java index 9dc2f69..f083e76 100644 --- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java +++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java @@ -39,10 +39,15 @@ public class OuterClass { public InnerClass() { } - // Inner.get returns 1+2=3 + a + b + // Inner.get returns 2 + 1 + a + b public int get(int a, long b) { return 2 + mOuterValue + a + (int) b; } } + + @SuppressWarnings("unused") + private String privateMethod() { + return "outerPrivateMethod"; + } } diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_Delegate.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_Delegate.java index 3252d87..774be8e 100644 --- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_Delegate.java +++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_Delegate.java @@ -26,5 +26,9 @@ public class OuterClass_Delegate { public static int get(OuterClass instance, int a, long b) { return 4 + a + (int) b; } + + public static String privateMethod(OuterClass instance) { + return "outerPrivate_Delegate"; + } } |