Support Locale.toLanguageTag on Java 6
Change-Id: I255e79e2c288cd24b350b7c26128bbbb0b2cb9a3
(cherry picked from commit 5cd9dde5a2)
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (C) 2014 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.layoutlib.bridge.android;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import com.ibm.icu.util.ULocale;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
public class AndroidLocale {
|
||||
|
||||
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
|
||||
|
||||
@@ -726,9 +726,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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