summaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tools')
-rw-r--r--tools/aapt/AaptAssets.cpp8
-rw-r--r--tools/aapt/Bundle.h9
-rw-r--r--tools/aapt/Main.cpp13
-rw-r--r--tools/aapt/Resource.cpp13
-rw-r--r--tools/aapt/ResourceTable.cpp34
-rw-r--r--tools/aapt/XMLNode.cpp9
-rw-r--r--tools/layoutlib/create/README.txt34
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java3
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java62
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter2.java (renamed from tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java)255
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/StubMethodAdapter.java13
-rw-r--r--tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java58
-rw-r--r--tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java7
-rw-r--r--tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_Delegate.java4
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>&lt;className&gt;_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>&lt;className&gt;_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";
+ }
}