Support Locale.toLanguageTag on Java 6
Change-Id: I255e79e2c288cd24b350b7c26128bbbb0b2cb9a3
This commit is contained in:
@@ -1711,7 +1711,7 @@ public class Resources {
|
||||
|
||||
String locale = null;
|
||||
if (mConfiguration.locale != null) {
|
||||
locale = adjustLanguageTag(localeToLanguageTag(mConfiguration.locale));
|
||||
locale = adjustLanguageTag(mConfiguration.locale.toLanguageTag());
|
||||
}
|
||||
int width, height;
|
||||
if (mMetrics.widthPixels >= mMetrics.heightPixels) {
|
||||
@@ -1800,12 +1800,6 @@ public class Resources {
|
||||
}
|
||||
}
|
||||
|
||||
// Locale.toLanguageTag() is not available in Java6. LayoutLib overrides
|
||||
// this method to enable users to use Java6.
|
||||
private String localeToLanguageTag(Locale locale) {
|
||||
return locale.toLanguageTag();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code Locale.toLanguageTag} will transform the obsolete (and deprecated)
|
||||
* language codes "in", "ji" and "iw" to "id", "yi" and "he" respectively.
|
||||
|
||||
@@ -14,24 +14,23 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.content.res;
|
||||
package com.android.layoutlib.bridge.android;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
|
||||
import com.ibm.icu.util.ULocale;
|
||||
|
||||
/**
|
||||
* Delegate used to provide new implementation of a select few methods of {@link Resources}
|
||||
*
|
||||
* Through the layoutlib_create tool, the original methods of Resources have been replaced
|
||||
* by calls to methods of the same name in this delegate class.
|
||||
* This class provides an alternate implementation for {@code java.util.Locale#toLanguageTag}
|
||||
* which is only available after Java 6.
|
||||
*
|
||||
* The create tool re-writes references to the above mentioned method to this one. Hence it's
|
||||
* imperative that this class is not deleted unless the create tool is modified.
|
||||
*/
|
||||
public class Resources_Delegate {
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
public class AndroidLocale {
|
||||
|
||||
@LayoutlibDelegate
|
||||
/*package*/ static String localeToLanguageTag(Resources res, Locale locale) {
|
||||
public static String toLanguageTag(Locale locale) {
|
||||
return ULocale.forLocale(locale).toLanguageTag();
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,8 @@ The ASM library is used to do the bytecode modification using its visitor patter
|
||||
|
||||
The layoutlib_create is *NOT* generic. There is no configuration file. Instead all the configuration
|
||||
is done in the main() method and the CreateInfo structure is expected to change with the Android
|
||||
platform as new classes are added, changed or removed.
|
||||
platform as new classes are added, changed or removed. Some configuration that may be platform
|
||||
dependent is also present elsewhere in code.
|
||||
|
||||
The resulting JAR is used by layoutlib_bridge (a.k.a. "the bridge"), also part of the platform, that
|
||||
provides all the necessary missing implementation for rendering graphics in Eclipse.
|
||||
@@ -95,7 +96,7 @@ The generator is constructed from a CreateInfo struct that acts as a config file
|
||||
- specific classes to refactor.
|
||||
|
||||
Each of these are specific strategies we use to be able to modify the Android code to fit within the
|
||||
Eclipse renderer. These strategies are explained beow.
|
||||
Eclipse renderer. These strategies are explained below.
|
||||
|
||||
The core method of the generator is transform(): it takes an input ASM ClassReader and modifies it
|
||||
to produce a byte array suitable for the final JAR file.
|
||||
@@ -130,9 +131,11 @@ the StackMapTable correctly and Java 7 VM enforces that classes with version gre
|
||||
valid StackMapTable. As a side benefit of this, we can continue to support Java 6 because Java 7 on
|
||||
Mac has horrible font rendering support.
|
||||
|
||||
ReplaceMethodCallsAdapter replaces calls to certain methods. Currently, it only rewrites calls to
|
||||
specialized versions of java.lang.System.arraycopy(), which are not part of the Desktop VM to call
|
||||
the more general method java.lang.System.arraycopy(Ljava/lang/Object;ILjava/lang/Object;II)V.
|
||||
ReplaceMethodCallsAdapter replaces calls to certain methods. This is different from the
|
||||
DelegateMethodAdapter since it doesn't preserve the original copy of the method and more importantly
|
||||
changes the calls to a method in each class instead of changing the implementation of the method.
|
||||
This is useful for methods in the Java namespace where we cannot add delegates. The configuration
|
||||
for this is not done through the CreateInfo class, but done in the ReplaceMethodAdapter.
|
||||
|
||||
The ClassAdapters are chained together to achieve the desired output. (Look at section 2.2.7
|
||||
Transformation chains in the asm user guide, link in the References.) The order of execution of
|
||||
|
||||
@@ -724,9 +724,8 @@ public class AsmAnalyzer {
|
||||
considerDesc(desc);
|
||||
|
||||
|
||||
// Check if method is a specialized version of java.lang.System.arrayCopy()
|
||||
if (owner.equals("java/lang/System") && name.equals("arraycopy")
|
||||
&& !desc.equals("(Ljava/lang/Object;ILjava/lang/Object;II)V")) {
|
||||
// Check if method needs to replaced by a call to a different method.
|
||||
if (ReplaceMethodCallsAdapter.isReplacementNeeded(owner, name, desc)) {
|
||||
mReplaceMethodCallClasses.add(mOwnerClass);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,7 +126,6 @@ public final class CreateInfo implements ICreateInfo {
|
||||
"android.content.res.Resources$Theme#obtainStyledAttributes",
|
||||
"android.content.res.Resources$Theme#resolveAttribute",
|
||||
"android.content.res.Resources$Theme#resolveAttributes",
|
||||
"android.content.res.Resources#localeToLanguageTag",
|
||||
"android.content.res.AssetManager#newTheme",
|
||||
"android.content.res.AssetManager#deleteTheme",
|
||||
"android.content.res.AssetManager#applyThemeStyle",
|
||||
|
||||
@@ -20,12 +20,15 @@ import org.objectweb.asm.ClassVisitor;
|
||||
import org.objectweb.asm.MethodVisitor;
|
||||
import org.objectweb.asm.Opcodes;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Replaces calls to certain methods that do not exist in the Desktop VM.
|
||||
* Replaces calls to certain methods that do not exist in the Desktop VM. Useful for methods in the
|
||||
* "java" package.
|
||||
*/
|
||||
public class ReplaceMethodCallsAdapter extends ClassVisitor {
|
||||
|
||||
@@ -37,6 +40,60 @@ public class ReplaceMethodCallsAdapter extends ClassVisitor {
|
||||
"([CI[CII)V", "([BI[BII)V", "([SI[SII)V", "([II[III)V",
|
||||
"([JI[JII)V", "([FI[FII)V", "([DI[DII)V", "([ZI[ZII)V"));
|
||||
|
||||
private static final List<MethodReplacer> METHOD_REPLACERS = new ArrayList<MethodReplacer>(2);
|
||||
|
||||
// Static initialization block to initialize METHOD_REPLACERS.
|
||||
static {
|
||||
// Case 1: java.lang.System.arraycopy()
|
||||
METHOD_REPLACERS.add(new MethodReplacer() {
|
||||
@Override
|
||||
public boolean isNeeded(String owner, String name, String desc) {
|
||||
return owner.equals("java/lang/System") && name.equals("arraycopy") &&
|
||||
ARRAYCOPY_DESCRIPTORS.contains(desc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void replace(int opcode, String owner, String name, String desc,
|
||||
int[] opcodeOut, String[] output) {
|
||||
assert isNeeded(owner, name, desc) && output.length == 3
|
||||
&& opcodeOut.length == 1;
|
||||
opcodeOut[0] = opcode;
|
||||
output[0] = owner;
|
||||
output[1] = name;
|
||||
output[2] = "(Ljava/lang/Object;ILjava/lang/Object;II)V";
|
||||
}
|
||||
});
|
||||
|
||||
// Case 2: java.util.Locale.toLanguageTag()
|
||||
METHOD_REPLACERS.add(new MethodReplacer() {
|
||||
@Override
|
||||
public boolean isNeeded(String owner, String name, String desc) {
|
||||
return owner.equals("java/util/Locale") && name.equals("toLanguageTag") &&
|
||||
"()Ljava/lang/String;".equals(desc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void replace(int opcode, String owner, String name, String desc,
|
||||
int[] opcodeOut, String[] output) {
|
||||
assert isNeeded(owner, name, desc) && output.length == 3
|
||||
&& opcodeOut.length == 1;
|
||||
opcodeOut[0] = Opcodes.INVOKESTATIC;
|
||||
output[0] = "com.android.layoutlib.bridge.android.AndroidLocale";
|
||||
output[1] = name;
|
||||
output[2] = "(Ljava/util/Locale;)Ljava/lang/String;";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static boolean isReplacementNeeded(String owner, String name, String desc) {
|
||||
for (MethodReplacer replacer : METHOD_REPLACERS) {
|
||||
if (replacer.isNeeded(owner, name, desc)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public ReplaceMethodCallsAdapter(ClassVisitor cv) {
|
||||
super(Opcodes.ASM4, cv);
|
||||
}
|
||||
@@ -56,13 +113,34 @@ public class ReplaceMethodCallsAdapter extends ClassVisitor {
|
||||
@Override
|
||||
public void visitMethodInsn(int opcode, String owner, String name, String desc) {
|
||||
// Check if method is a specialized version of java.lang.System.arrayCopy
|
||||
if (owner.equals("java/lang/System") && name.equals("arraycopy")) {
|
||||
|
||||
if (ARRAYCOPY_DESCRIPTORS.contains(desc)) {
|
||||
desc = "(Ljava/lang/Object;ILjava/lang/Object;II)V";
|
||||
for (MethodReplacer replacer : METHOD_REPLACERS) {
|
||||
if (replacer.isNeeded(owner, name, desc)) {
|
||||
String[] output = new String[3];
|
||||
int[] opcodeOut = new int[1];
|
||||
replacer.replace(opcode, owner, name, desc, opcodeOut, output);
|
||||
opcode = opcodeOut[0];
|
||||
owner = output[0];
|
||||
name = output[1];
|
||||
desc = output[2];
|
||||
break;
|
||||
}
|
||||
}
|
||||
super.visitMethodInsn(opcode, owner, name, desc);
|
||||
}
|
||||
}
|
||||
|
||||
private interface MethodReplacer {
|
||||
public boolean isNeeded(String owner, String name, String desc);
|
||||
|
||||
/**
|
||||
* This method must update the values of the output arrays with the new values of method
|
||||
* attributes - opcode, owner, name and desc.
|
||||
* @param opcodeOut An array that will contain the new value of the opcode. The size of
|
||||
* the array must be 1.
|
||||
* @param output An array that will contain the new values of the owner, name and desc in
|
||||
* that order. The size of the array must be 3.
|
||||
*/
|
||||
public void replace(int opcode, String owner, String name, String desc, int[] opcodeOut,
|
||||
String[] output);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user