() {
+ @Override protected char[] initialValue() {
+ return new char[20]; // Maximum length of a base-10 long.
+ }
+ };
+
+ /**
+ * These tables are used to special-case toString computation for
+ * small values. This serves three purposes: it reduces memory usage;
+ * it increases performance for small values; and it decreases the
+ * number of comparisons required to do the length computation.
+ * Elements of this table are lazily initialized on first use.
+ * No locking is necessary, i.e., we use the non-volatile, racy
+ * single-check idiom.
+ */
+ private static final String[] SMALL_NONNEGATIVE_VALUES = new String[100];
+ private static final String[] SMALL_NEGATIVE_VALUES = new String[100];
+
+ /** TENS[i] contains the tens digit of the number i, 0 <= i <= 99. */
+ private static final char[] TENS = {
+ '0', '0', '0', '0', '0', '0', '0', '0', '0', '0',
+ '1', '1', '1', '1', '1', '1', '1', '1', '1', '1',
+ '2', '2', '2', '2', '2', '2', '2', '2', '2', '2',
+ '3', '3', '3', '3', '3', '3', '3', '3', '3', '3',
+ '4', '4', '4', '4', '4', '4', '4', '4', '4', '4',
+ '5', '5', '5', '5', '5', '5', '5', '5', '5', '5',
+ '6', '6', '6', '6', '6', '6', '6', '6', '6', '6',
+ '7', '7', '7', '7', '7', '7', '7', '7', '7', '7',
+ '8', '8', '8', '8', '8', '8', '8', '8', '8', '8',
+ '9', '9', '9', '9', '9', '9', '9', '9', '9', '9'
+ };
+
+ /** Ones [i] contains the tens digit of the number i, 0 <= i <= 99. */
+ private static final char[] ONES = {
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ };
+
+ /**
+ * Table for MOD / DIV 10 computation described in Section 10-21
+ * of Hank Warren's "Hacker's Delight" online addendum.
+ * http://www.hackersdelight.org/divcMore.pdf
+ */
+ private static final char[] MOD_10_TABLE = {
+ 0, 1, 2, 2, 3, 3, 4, 5, 5, 6, 7, 7, 8, 8, 9, 0
+ };
+
+ /**
+ * The digits for every supported radix.
+ */
+ private static final char[] DIGITS = {
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
+ 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
+ 'u', 'v', 'w', 'x', 'y', 'z'
+ };
+
+ private static final char[] UPPER_CASE_DIGITS = {
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
+ 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
+ 'U', 'V', 'W', 'X', 'Y', 'Z'
+ };
+
+ private IntegralToString() {
+ }
+
+ /**
+ * Equivalent to Integer.toString(i, radix).
+ */
+ public static String intToString(int i, int radix) {
+ if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX) {
+ radix = 10;
+ }
+ if (radix == 10) {
+ return intToString(i);
+ }
+
+ /*
+ * If i is positive, negate it. This is the opposite of what one might
+ * expect. It is necessary because the range of the negative values is
+ * strictly larger than that of the positive values: there is no
+ * positive value corresponding to Integer.MIN_VALUE.
+ */
+ boolean negative = false;
+ if (i < 0) {
+ negative = true;
+ } else {
+ i = -i;
+ }
+
+ int bufLen = radix < 8 ? 33 : 12; // Max chars in result (conservative)
+ char[] buf = new char[bufLen];
+ int cursor = bufLen;
+
+ do {
+ int q = i / radix;
+ buf[--cursor] = DIGITS[radix * q - i];
+ i = q;
+ } while (i != 0);
+
+ if (negative) {
+ buf[--cursor] = '-';
+ }
+
+ return new String(buf, cursor, bufLen - cursor);
+ }
+
+ /**
+ * Equivalent to Integer.toString(i).
+ */
+ public static String intToString(int i) {
+ return convertInt(null, i);
+ }
+
+ /**
+ * Equivalent to sb.append(Integer.toString(i)).
+ */
+ public static void appendInt(StringBuilder sb, int i) {
+ convertInt(sb, i);
+ }
+
+ /**
+ * Returns the string representation of i and leaves sb alone if sb is null.
+ * Returns null and appends the string representation of i to sb if sb is non-null.
+ */
+ private static String convertInt(StringBuilder sb, int i) {
+ boolean negative = false;
+ String quickResult = null;
+ if (i < 0) {
+ negative = true;
+ i = -i;
+ if (i < 100) {
+ if (i < 0) {
+ // If -n is still negative, n is Integer.MIN_VALUE
+ quickResult = "-2147483648";
+ } else {
+ quickResult = SMALL_NEGATIVE_VALUES[i];
+ if (quickResult == null) {
+ SMALL_NEGATIVE_VALUES[i] = quickResult =
+ i < 10 ? stringOf('-', ONES[i]) : stringOf('-', TENS[i], ONES[i]);
+ }
+ }
+ }
+ } else {
+ if (i < 100) {
+ quickResult = SMALL_NONNEGATIVE_VALUES[i];
+ if (quickResult == null) {
+ SMALL_NONNEGATIVE_VALUES[i] = quickResult =
+ i < 10 ? stringOf(ONES[i]) : stringOf(TENS[i], ONES[i]);
+ }
+ }
+ }
+ if (quickResult != null) {
+ if (sb != null) {
+ sb.append(quickResult);
+ return null;
+ }
+ return quickResult;
+ }
+
+ int bufLen = 11; // Max number of chars in result
+ char[] buf = (sb != null) ? BUFFER.get() : new char[bufLen];
+ int cursor = bufLen;
+
+ // Calculate digits two-at-a-time till remaining digits fit in 16 bits
+ while (i >= (1 << 16)) {
+ // Compute q = n/100 and r = n % 100 as per "Hacker's Delight" 10-8
+ int q = (int) ((0x51EB851FL * i) >>> 37);
+ int r = i - 100*q;
+ buf[--cursor] = ONES[r];
+ buf[--cursor] = TENS[r];
+ i = q;
+ }
+
+ // Calculate remaining digits one-at-a-time for performance
+ while (i != 0) {
+ // Compute q = n/10 and r = n % 10 as per "Hacker's Delight" 10-8
+ int q = (0xCCCD * i) >>> 19;
+ int r = i - 10*q;
+ buf[--cursor] = DIGITS[r];
+ i = q;
+ }
+
+ if (negative) {
+ buf[--cursor] = '-';
+ }
+
+ if (sb != null) {
+ sb.append(buf, cursor, bufLen - cursor);
+ return null;
+ } else {
+ return new String(buf, cursor, bufLen - cursor);
+ }
+ }
+
+ /**
+ * Equivalent to Long.toString(v, radix).
+ */
+ public static String longToString(long v, int radix) {
+ int i = (int) v;
+ if (i == v) {
+ return intToString(i, radix);
+ }
+
+ if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX) {
+ radix = 10;
+ }
+ if (radix == 10) {
+ return longToString(v);
+ }
+
+ /*
+ * If v is positive, negate it. This is the opposite of what one might
+ * expect. It is necessary because the range of the negative values is
+ * strictly larger than that of the positive values: there is no
+ * positive value corresponding to Integer.MIN_VALUE.
+ */
+ boolean negative = false;
+ if (v < 0) {
+ negative = true;
+ } else {
+ v = -v;
+ }
+
+ int bufLen = radix < 8 ? 65 : 23; // Max chars in result (conservative)
+ char[] buf = new char[bufLen];
+ int cursor = bufLen;
+
+ do {
+ long q = v / radix;
+ buf[--cursor] = DIGITS[(int) (radix * q - v)];
+ v = q;
+ } while (v != 0);
+
+ if (negative) {
+ buf[--cursor] = '-';
+ }
+
+ return new String(buf, cursor, bufLen - cursor);
+ }
+
+ /**
+ * Equivalent to Long.toString(l).
+ */
+ public static String longToString(long l) {
+ return convertLong(null, l);
+ }
+
+ /**
+ * Equivalent to sb.append(Long.toString(l)).
+ */
+ public static void appendLong(StringBuilder sb, long l) {
+ convertLong(sb, l);
+ }
+
+ /**
+ * Returns the string representation of n and leaves sb alone if sb is null.
+ * Returns null and appends the string representation of n to sb if sb is non-null.
+ */
+ private static String convertLong(StringBuilder sb, long n) {
+ int i = (int) n;
+ if (i == n) {
+ return convertInt(sb, i);
+ }
+
+ boolean negative = (n < 0);
+ if (negative) {
+ n = -n;
+ if (n < 0) {
+ // If -n is still negative, n is Long.MIN_VALUE
+ String quickResult = "-9223372036854775808";
+ if (sb != null) {
+ sb.append(quickResult);
+ return null;
+ }
+ return quickResult;
+ }
+ }
+
+ int bufLen = 20; // Maximum number of chars in result
+ char[] buf = (sb != null) ? BUFFER.get() : new char[bufLen];
+
+ int low = (int) (n % 1000000000); // Extract low-order 9 digits
+ int cursor = intIntoCharArray(buf, bufLen, low);
+
+ // Zero-pad Low order part to 9 digits
+ while (cursor != (bufLen - 9)) {
+ buf[--cursor] = '0';
+ }
+
+ /*
+ * The remaining digits are (n - low) / 1,000,000,000. This
+ * "exact division" is done as per the online addendum to Hank Warren's
+ * "Hacker's Delight" 10-20, http://www.hackersdelight.org/divcMore.pdf
+ */
+ n = ((n - low) >>> 9) * 0x8E47CE423A2E9C6DL;
+
+ /*
+ * If the remaining digits fit in an int, emit them using a
+ * single call to intIntoCharArray. Otherwise, strip off the
+ * low-order digit, put it in buf, and then call intIntoCharArray
+ * on the remaining digits (which now fit in an int).
+ */
+ if ((n & (-1L << 32)) == 0) {
+ cursor = intIntoCharArray(buf, cursor, (int) n);
+ } else {
+ /*
+ * Set midDigit to n % 10
+ */
+ int lo32 = (int) n;
+ int hi32 = (int) (n >>> 32);
+
+ // midDigit = ((unsigned) low32) % 10, per "Hacker's Delight" 10-21
+ int midDigit = MOD_10_TABLE[(0x19999999 * lo32 + (lo32 >>> 1) + (lo32 >>> 3)) >>> 28];
+
+ // Adjust midDigit for hi32. (assert hi32 == 1 || hi32 == 2)
+ midDigit -= hi32 << 2; // 1L << 32 == -4 MOD 10
+ if (midDigit < 0) {
+ midDigit += 10;
+ }
+ buf[--cursor] = DIGITS[midDigit];
+
+ // Exact division as per Warren 10-20
+ int rest = ((int) ((n - midDigit) >>> 1)) * 0xCCCCCCCD;
+ cursor = intIntoCharArray(buf, cursor, rest);
+ }
+
+ if (negative) {
+ buf[--cursor] = '-';
+ }
+ if (sb != null) {
+ sb.append(buf, cursor, bufLen - cursor);
+ return null;
+ } else {
+ return new String(buf, cursor, bufLen - cursor);
+ }
+ }
+
+ /**
+ * Inserts the unsigned decimal integer represented by n into the specified
+ * character array starting at position cursor. Returns the index after
+ * the last character inserted (i.e., the value to pass in as cursor the
+ * next time this method is called). Note that n is interpreted as a large
+ * positive integer (not a negative integer) if its sign bit is set.
+ */
+ private static int intIntoCharArray(char[] buf, int cursor, int n) {
+ // Calculate digits two-at-a-time till remaining digits fit in 16 bits
+ while ((n & 0xffff0000) != 0) {
+ /*
+ * Compute q = n/100 and r = n % 100 as per "Hacker's Delight" 10-8.
+ * This computation is slightly different from the corresponding
+ * computation in intToString: the shifts before and after
+ * multiply can't be combined, as that would yield the wrong result
+ * if n's sign bit were set.
+ */
+ int q = (int) ((0x51EB851FL * (n >>> 2)) >>> 35);
+ int r = n - 100*q;
+ buf[--cursor] = ONES[r];
+ buf[--cursor] = TENS[r];
+ n = q;
+ }
+
+ // Calculate remaining digits one-at-a-time for performance
+ while (n != 0) {
+ // Compute q = n / 10 and r = n % 10 as per "Hacker's Delight" 10-8
+ int q = (0xCCCD * n) >>> 19;
+ int r = n - 10*q;
+ buf[--cursor] = DIGITS[r];
+ n = q;
+ }
+ return cursor;
+ }
+
+ public static String intToBinaryString(int i) {
+ int bufLen = 32; // Max number of binary digits in an int
+ char[] buf = new char[bufLen];
+ int cursor = bufLen;
+
+ do {
+ buf[--cursor] = DIGITS[i & 1];
+ } while ((i >>>= 1) != 0);
+
+ return new String(buf, cursor, bufLen - cursor);
+ }
+
+ public static String longToBinaryString(long v) {
+ int i = (int) v;
+ if (v >= 0 && i == v) {
+ return intToBinaryString(i);
+ }
+
+ int bufLen = 64; // Max number of binary digits in a long
+ char[] buf = new char[bufLen];
+ int cursor = bufLen;
+
+ do {
+ buf[--cursor] = DIGITS[((int) v) & 1];
+ } while ((v >>>= 1) != 0);
+
+ return new String(buf, cursor, bufLen - cursor);
+ }
+
+ public static StringBuilder appendByteAsHex(StringBuilder sb, byte b, boolean upperCase) {
+ char[] digits = upperCase ? UPPER_CASE_DIGITS : DIGITS;
+ sb.append(digits[(b >> 4) & 0xf]);
+ sb.append(digits[b & 0xf]);
+ return sb;
+ }
+
+ public static String byteToHexString(byte b, boolean upperCase) {
+ char[] digits = upperCase ? UPPER_CASE_DIGITS : DIGITS;
+ char[] buf = new char[2]; // We always want two digits.
+ buf[0] = digits[(b >> 4) & 0xf];
+ buf[1] = digits[b & 0xf];
+ return new String(buf, 0, 2);
+ }
+
+ public static String bytesToHexString(byte[] bytes, boolean upperCase) {
+ char[] digits = upperCase ? UPPER_CASE_DIGITS : DIGITS;
+ char[] buf = new char[bytes.length * 2];
+ int c = 0;
+ for (byte b : bytes) {
+ buf[c++] = digits[(b >> 4) & 0xf];
+ buf[c++] = digits[b & 0xf];
+ }
+ return new String(buf);
+ }
+
+ public static String intToHexString(int i, boolean upperCase, int minWidth) {
+ int bufLen = 8; // Max number of hex digits in an int
+ char[] buf = new char[bufLen];
+ int cursor = bufLen;
+
+ char[] digits = upperCase ? UPPER_CASE_DIGITS : DIGITS;
+ do {
+ buf[--cursor] = digits[i & 0xf];
+ } while ((i >>>= 4) != 0 || (bufLen - cursor < minWidth));
+
+ return new String(buf, cursor, bufLen - cursor);
+ }
+
+ public static String longToHexString(long v) {
+ int i = (int) v;
+ if (v >= 0 && i == v) {
+ return intToHexString(i, false, 0);
+ }
+
+ int bufLen = 16; // Max number of hex digits in a long
+ char[] buf = new char[bufLen];
+ int cursor = bufLen;
+
+ do {
+ buf[--cursor] = DIGITS[((int) v) & 0xF];
+ } while ((v >>>= 4) != 0);
+
+ return new String(buf, cursor, bufLen - cursor);
+ }
+
+ public static String intToOctalString(int i) {
+ int bufLen = 11; // Max number of octal digits in an int
+ char[] buf = new char[bufLen];
+ int cursor = bufLen;
+
+ do {
+ buf[--cursor] = DIGITS[i & 7];
+ } while ((i >>>= 3) != 0);
+
+ return new String(buf, cursor, bufLen - cursor);
+ }
+
+ public static String longToOctalString(long v) {
+ int i = (int) v;
+ if (v >= 0 && i == v) {
+ return intToOctalString(i);
+ }
+ int bufLen = 22; // Max number of octal digits in a long
+ char[] buf = new char[bufLen];
+ int cursor = bufLen;
+
+ do {
+ buf[--cursor] = DIGITS[((int) v) & 7];
+ } while ((v >>>= 3) != 0);
+
+ return new String(buf, cursor, bufLen - cursor);
+ }
+
+ private static String stringOf(char... args) {
+ return new String(args, 0, args.length);
+ }
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/java/ModifiedUtf8.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/java/ModifiedUtf8.java
new file mode 100644
index 0000000000000..77c4f3a03964a
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/java/ModifiedUtf8.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2013 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.tools.layoutlib.java;
+
+import java.io.UTFDataFormatException;
+import java.nio.ByteOrder;
+
+/**
+ * Defines the same class as the java.nio.charset.ModifiedUtf8 which was added in
+ * Dalvik VM. This hack, provides a replacement for that class which can't be
+ * loaded in the standard JVM since it's in the java package and standard JVM
+ * doesn't have it. An implementation of the native methods in the original
+ * class has been added.
+ *
+ * Extracted from API level 17, file:
+ * platform/libcore/luni/src/main/java/java/nio/charset/ModifiedUtf8
+ */
+public class ModifiedUtf8 {
+ /**
+ * Decodes a byte array containing modified UTF-8 bytes into a string.
+ *
+ * Note that although this method decodes the (supposedly impossible) zero byte to U+0000,
+ * that's what the RI does too.
+ */
+ public static String decode(byte[] in, char[] out, int offset, int utfSize) throws UTFDataFormatException {
+ int count = 0, s = 0, a;
+ while (count < utfSize) {
+ if ((out[s] = (char) in[offset + count++]) < '\u0080') {
+ s++;
+ } else if (((a = out[s]) & 0xe0) == 0xc0) {
+ if (count >= utfSize) {
+ throw new UTFDataFormatException("bad second byte at " + count);
+ }
+ int b = in[offset + count++];
+ if ((b & 0xC0) != 0x80) {
+ throw new UTFDataFormatException("bad second byte at " + (count - 1));
+ }
+ out[s++] = (char) (((a & 0x1F) << 6) | (b & 0x3F));
+ } else if ((a & 0xf0) == 0xe0) {
+ if (count + 1 >= utfSize) {
+ throw new UTFDataFormatException("bad third byte at " + (count + 1));
+ }
+ int b = in[offset + count++];
+ int c = in[offset + count++];
+ if (((b & 0xC0) != 0x80) || ((c & 0xC0) != 0x80)) {
+ throw new UTFDataFormatException("bad second or third byte at " + (count - 2));
+ }
+ out[s++] = (char) (((a & 0x0F) << 12) | ((b & 0x3F) << 6) | (c & 0x3F));
+ } else {
+ throw new UTFDataFormatException("bad byte at " + (count - 1));
+ }
+ }
+ return new String(out, 0, s);
+ }
+
+ /**
+ * Returns the number of bytes the modified UTF-8 representation of 's' would take. Note
+ * that this is just the space for the bytes representing the characters, not the length
+ * which precedes those bytes, because different callers represent the length differently,
+ * as two, four, or even eight bytes. If {@code shortLength} is true, we'll throw an
+ * exception if the string is too long for its length to be represented by a short.
+ */
+ public static long countBytes(String s, boolean shortLength) throws UTFDataFormatException {
+ long result = 0;
+ final int length = s.length();
+ for (int i = 0; i < length; ++i) {
+ char ch = s.charAt(i);
+ if (ch != 0 && ch <= 127) { // U+0000 uses two bytes.
+ ++result;
+ } else if (ch <= 2047) {
+ result += 2;
+ } else {
+ result += 3;
+ }
+ if (shortLength && result > 65535) {
+ throw new UTFDataFormatException("String more than 65535 UTF bytes long");
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Encodes the modified UTF-8 bytes corresponding to string {@code s} into the
+ * byte array {@code dst}, starting at the given {@code offset}.
+ */
+ public static void encode(byte[] dst, int offset, String s) {
+ final int length = s.length();
+ for (int i = 0; i < length; i++) {
+ char ch = s.charAt(i);
+ if (ch != 0 && ch <= 127) { // U+0000 uses two bytes.
+ dst[offset++] = (byte) ch;
+ } else if (ch <= 2047) {
+ dst[offset++] = (byte) (0xc0 | (0x1f & (ch >> 6)));
+ dst[offset++] = (byte) (0x80 | (0x3f & ch));
+ } else {
+ dst[offset++] = (byte) (0xe0 | (0x0f & (ch >> 12)));
+ dst[offset++] = (byte) (0x80 | (0x3f & (ch >> 6)));
+ dst[offset++] = (byte) (0x80 | (0x3f & ch));
+ }
+ }
+ }
+
+ /**
+ * Returns an array containing the modified UTF-8 form of {@code s}, using a
+ * big-endian 16-bit length. Throws UTFDataFormatException if {@code s} is too long
+ * for a two-byte length.
+ */
+ public static byte[] encode(String s) throws UTFDataFormatException {
+ int utfCount = (int) ModifiedUtf8.countBytes(s, true);
+ // Original statement used libcore.io.SizeOf.SHORT. Since we don't have that, we
+ // substitute it's value directly, which is 2.
+ byte[] result = new byte[2 + utfCount];
+ // The original statement had libcore.io.Memory.pokeShort(). We provide it's implementation
+ // below in a private method.
+ pokeShort(result, 0, (short) utfCount, ByteOrder.BIG_ENDIAN);
+ ModifiedUtf8.encode(result, 2, s);
+ return result;
+ }
+
+ // Copied from libcore.io.Memory.pokeShort().
+ private static void pokeShort(byte[] dst, int offset, short value, ByteOrder order) {
+ if (order == ByteOrder.BIG_ENDIAN) {
+ dst[offset++] = (byte) ((value >> 8) & 0xff);
+ dst[offset ] = (byte) ((value >> 0) & 0xff);
+ } else {
+ dst[offset++] = (byte) ((value >> 0) & 0xff);
+ dst[offset ] = (byte) ((value >> 8) & 0xff);
+ }
+ }
+
+ private ModifiedUtf8() {
+ }
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/java/UnsafeByteSequence.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/java/UnsafeByteSequence.java
new file mode 100644
index 0000000000000..0e09080c5111a
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/java/UnsafeByteSequence.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2013 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.tools.layoutlib.java;
+
+import java.nio.charset.Charset;
+
+/**
+ * Defines the same class as the java.lang.UnsafeByteSequence which was added in
+ * Dalvik VM. This hack, provides a replacement for that class which can't be
+ * loaded in the standard JVM since it's in the java package and standard JVM
+ * doesn't have it.
+ *
+ * Extracted from API level 18, file:
+ * platform/libcore/luni/src/main/java/java/lang/UnsafeByteSequence.java
+ */
+public class UnsafeByteSequence {
+ private byte[] bytes;
+ private int count;
+
+ public UnsafeByteSequence(int initialCapacity) {
+ this.bytes = new byte[initialCapacity];
+ }
+
+ public int size() {
+ return count;
+ }
+
+ /**
+ * Moves the write pointer back to the beginning of the sequence,
+ * but without resizing or reallocating the buffer.
+ */
+ public void rewind() {
+ count = 0;
+ }
+
+ public void write(byte[] buffer, int offset, int length) {
+ if (count + length >= bytes.length) {
+ byte[] newBytes = new byte[(count + length) * 2];
+ System.arraycopy(bytes, 0, newBytes, 0, count);
+ bytes = newBytes;
+ }
+ System.arraycopy(buffer, offset, bytes, count, length);
+ count += length;
+ }
+
+ public void write(int b) {
+ if (count == bytes.length) {
+ byte[] newBytes = new byte[count * 2];
+ System.arraycopy(bytes, 0, newBytes, 0, count);
+ bytes = newBytes;
+ }
+ bytes[count++] = (byte) b;
+ }
+
+ public byte[] toByteArray() {
+ if (count == bytes.length) {
+ return bytes;
+ }
+ byte[] result = new byte[count];
+ System.arraycopy(bytes, 0, result, 0, count);
+ return result;
+ }
+
+ public String toString(Charset cs) {
+ return new String(bytes, 0, count, cs);
+ }
+}
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java
index d6dba6a9fb341..005fc9dadab41 100644
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java
@@ -31,7 +31,9 @@ import org.objectweb.asm.ClassReader;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.Map;
+import java.util.Set;
import java.util.TreeMap;
/**
@@ -51,8 +53,10 @@ public class AsmAnalyzerTest {
mOsJarPath = new ArrayList();
mOsJarPath.add(url.getFile());
+ Set excludeClasses = new HashSet(1);
+ excludeClasses.add("java.lang.JavaClass");
mAa = new AsmAnalyzer(mLog, mOsJarPath, null /* gen */,
- null /* deriveFrom */, null /* includeGlobs */ );
+ null /* deriveFrom */, null /* includeGlobs */, excludeClasses);
}
@After
@@ -64,6 +68,7 @@ public class AsmAnalyzerTest {
Map map = mAa.parseZip(mOsJarPath);
assertArrayEquals(new String[] {
+ "java.lang.JavaClass",
"mock_android.dummy.InnerTest",
"mock_android.dummy.InnerTest$DerivingClass",
"mock_android.dummy.InnerTest$MyGenerics1",
@@ -221,7 +226,11 @@ public class AsmAnalyzerTest {
for (ClassReader cr2 : in_deps.values()) {
cr2.accept(visitor, 0 /* flags */);
}
+ keep.putAll(new_keep);
assertArrayEquals(new String[] { }, out_deps.keySet().toArray());
+ assertArrayEquals(new String[] {
+ "mock_android.widget.TableLayout",
+ }, keep.keySet().toArray());
}
}
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java
index 7b76a5b2f9144..8a27173181a30 100644
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java
@@ -19,16 +19,29 @@ package com.android.tools.layoutlib.create;
import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertTrue;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.FieldVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Map;
import java.util.Set;
+import java.util.TreeMap;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
/**
* Unit tests for some methods of {@link AsmGenerator}.
@@ -40,6 +53,9 @@ public class AsmGeneratorTest {
private String mOsDestJar;
private File mTempFile;
+ // ASM internal name for the the class in java package that should be refactored.
+ private static final String JAVA_CLASS_NAME = "java/lang/JavaClass";
+
@Before
public void setUp() throws Exception {
mLog = new MockLog();
@@ -48,7 +64,7 @@ public class AsmGeneratorTest {
mOsJarPath = new ArrayList();
mOsJarPath.add(url.getFile());
- mTempFile = File.createTempFile("mock", "jar");
+ mTempFile = File.createTempFile("mock", ".jar");
mOsDestJar = mTempFile.getAbsolutePath();
mTempFile.deleteOnExit();
}
@@ -96,6 +112,11 @@ public class AsmGeneratorTest {
};
}
+ @Override
+ public String[] getJavaPkgClasses() {
+ return new String[0];
+ }
+
@Override
public String[] getDeleteReturns() {
// methods deleted from their return type.
@@ -109,11 +130,201 @@ public class AsmGeneratorTest {
null, // derived from
new String[] { // include classes
"**"
- });
+ },
+ new HashSet(0) /* excluded classes */);
aa.analyze();
agen.generate();
Set notRenamed = agen.getClassesNotRenamed();
assertArrayEquals(new String[] { "not/an/actual/ClassName" }, notRenamed.toArray());
+
+ }
+
+ @Test
+ public void testClassRefactoring() throws IOException, LogAbortException {
+ ICreateInfo ci = new ICreateInfo() {
+ @Override
+ public Class>[] getInjectedClasses() {
+ // classes to inject in the final JAR
+ return new Class>[] {
+ com.android.tools.layoutlib.create.dataclass.JavaClass.class
+ };
+ }
+
+ @Override
+ public String[] getDelegateMethods() {
+ return new String[0];
+ }
+
+ @Override
+ public String[] getDelegateClassNatives() {
+ return new String[0];
+ }
+
+ @Override
+ public String[] getOverriddenMethods() {
+ // methods to force override
+ return new String[0];
+ }
+
+ @Override
+ public String[] getRenamedClasses() {
+ // classes to rename (so that we can replace them)
+ return new String[0];
+ }
+
+ @Override
+ public String[] getJavaPkgClasses() {
+ // classes to refactor (so that we can replace them)
+ return new String[] {
+ "java.lang.JavaClass", "com.android.tools.layoutlib.create.dataclass.JavaClass",
+ };
+ }
+
+ @Override
+ public String[] getDeleteReturns() {
+ // methods deleted from their return type.
+ return new String[0];
+ }
+ };
+
+ AsmGenerator agen = new AsmGenerator(mLog, mOsDestJar, ci);
+
+ AsmAnalyzer aa = new AsmAnalyzer(mLog, mOsJarPath, agen,
+ null, // derived from
+ new String[] { // include classes
+ "**"
+ },
+ new HashSet(1));
+ aa.analyze();
+ agen.generate();
+ Map output = parseZip(mOsDestJar);
+ boolean injectedClassFound = false;
+ for (ClassReader cr: output.values()) {
+ TestClassVisitor cv = new TestClassVisitor();
+ cr.accept(cv, 0);
+ injectedClassFound |= cv.mInjectedClassFound;
+ }
+ assertTrue(injectedClassFound);
+ }
+
+ private Map parseZip(String jarPath) throws IOException {
+ TreeMap classes = new TreeMap();
+
+ ZipFile zip = new ZipFile(jarPath);
+ Enumeration extends ZipEntry> entries = zip.entries();
+ ZipEntry entry;
+ while (entries.hasMoreElements()) {
+ entry = entries.nextElement();
+ if (entry.getName().endsWith(".class")) {
+ ClassReader cr = new ClassReader(zip.getInputStream(entry));
+ String className = classReaderToClassName(cr);
+ classes.put(className, cr);
+ }
+ }
+
+ return classes;
+ }
+
+ private String classReaderToClassName(ClassReader classReader) {
+ if (classReader == null) {
+ return null;
+ } else {
+ return classReader.getClassName().replace('/', '.');
+ }
+ }
+
+ private class TestClassVisitor extends ClassVisitor {
+
+ boolean mInjectedClassFound = false;
+
+ TestClassVisitor() {
+ super(Opcodes.ASM4);
+ }
+
+ @Override
+ public void visit(int version, int access, String name, String signature,
+ String superName, String[] interfaces) {
+ assertTrue(!getBase(name).equals(JAVA_CLASS_NAME));
+ if (name.equals("com/android/tools/layoutlib/create/dataclass/JavaClass")) {
+ mInjectedClassFound = true;
+ }
+ super.visit(version, access, name, signature, superName, interfaces);
+ }
+
+ @Override
+ public FieldVisitor visitField(int access, String name, String desc,
+ String signature, Object value) {
+ assertTrue(testType(Type.getType(desc)));
+ return super.visitField(access, name, desc, signature, value);
+ }
+
+ @SuppressWarnings("hiding")
+ @Override
+ public MethodVisitor visitMethod(int access, String name, String desc,
+ String signature, String[] exceptions) {
+ MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
+ return new MethodVisitor(Opcodes.ASM4, mv) {
+
+ @Override
+ public void visitFieldInsn(int opcode, String owner, String name,
+ String desc) {
+ assertTrue(!getBase(owner).equals(JAVA_CLASS_NAME));
+ assertTrue(testType(Type.getType(desc)));
+ super.visitFieldInsn(opcode, owner, name, desc);
+ }
+
+ @Override
+ public void visitLdcInsn(Object cst) {
+ if (cst instanceof Type) {
+ assertTrue(testType((Type)cst));
+ }
+ super.visitLdcInsn(cst);
+ }
+
+ @Override
+ public void visitTypeInsn(int opcode, String type) {
+ assertTrue(!getBase(type).equals(JAVA_CLASS_NAME));
+ super.visitTypeInsn(opcode, type);
+ }
+
+ @Override
+ public void visitMethodInsn(int opcode, String owner, String name,
+ String desc) {
+ assertTrue(!getBase(owner).equals(JAVA_CLASS_NAME));
+ assertTrue(testType(Type.getType(desc)));
+ super.visitMethodInsn(opcode, owner, name, desc);
+ }
+
+ };
+ }
+
+ private boolean testType(Type type) {
+ int sort = type.getSort();
+ if (sort == Type.OBJECT) {
+ assertTrue(!getBase(type.getInternalName()).equals(JAVA_CLASS_NAME));
+ } else if (sort == Type.ARRAY) {
+ assertTrue(!getBase(type.getElementType().getInternalName())
+ .equals(JAVA_CLASS_NAME));
+ } else if (sort == Type.METHOD) {
+ boolean r = true;
+ for (Type t : type.getArgumentTypes()) {
+ r &= testType(t);
+ }
+ return r & testType(type.getReturnType());
+ }
+ return true;
+ }
+
+ private String getBase(String className) {
+ if (className == null) {
+ return null;
+ }
+ int pos = className.indexOf('$');
+ if (pos > 0) {
+ return className.substring(0, pos);
+ }
+ return className;
+ }
}
}
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/RenameClassAdapterTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/RenameClassAdapterTest.java
index 90c6a9c9ac7ce..6211e73417e45 100644
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/RenameClassAdapterTest.java
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/RenameClassAdapterTest.java
@@ -24,7 +24,7 @@ import org.junit.Before;
import org.junit.Test;
/**
- *
+ *
*/
public class RenameClassAdapterTest {
@@ -36,10 +36,10 @@ public class RenameClassAdapterTest {
mOuter = new RenameClassAdapter(null, // cv
"com.pack.Old",
"org.blah.New");
-
+
mInner = new RenameClassAdapter(null, // cv
"com.pack.Old$Inner",
- "org.blah.New$Inner");
+ "org.blah.New$Inner");
}
@After
@@ -72,7 +72,7 @@ public class RenameClassAdapterTest {
// arrays
assertEquals("[Lorg.blah.New;", mOuter.renameTypeDesc("[Lcom.pack.Old;"));
assertEquals("[[Lorg.blah.New;", mOuter.renameTypeDesc("[[Lcom.pack.Old;"));
-
+
assertEquals("[Lorg.blah.New;", mInner.renameTypeDesc("[Lcom.pack.Old;"));
assertEquals("[[Lorg.blah.New;", mInner.renameTypeDesc("[[Lcom.pack.Old;"));
}
@@ -93,10 +93,6 @@ public class RenameClassAdapterTest {
*/
@Test
public void testRenameInternalType() {
- // a descriptor is not left untouched
- assertEquals("Lorg.blah.New;", mOuter.renameInternalType("Lcom.pack.Old;"));
- assertEquals("Lorg.blah.New$Inner;", mOuter.renameInternalType("Lcom.pack.Old$Inner;"));
-
// an actual FQCN
assertEquals("org.blah.New", mOuter.renameInternalType("com.pack.Old"));
assertEquals("org.blah.New$Inner", mOuter.renameInternalType("com.pack.Old$Inner"));
@@ -115,6 +111,6 @@ public class RenameClassAdapterTest {
mOuter.renameMethodDesc("(IDLcom.pack.Old;[Lcom.pack.Old$Inner;)Lcom.pack.Old$Other;"));
}
-
+
}
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/JavaClass.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/JavaClass.java
new file mode 100644
index 0000000000000..9b5a91886b84c
--- /dev/null
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/JavaClass.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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.tools.layoutlib.create.dataclass;
+
+public final class JavaClass {
+
+ public static final String test = "test";
+}
diff --git a/tools/layoutlib/create/tests/data/mock_android.jar b/tools/layoutlib/create/tests/data/mock_android.jar
index a7ea74f4adf20..60d8efb1bb997 100644
Binary files a/tools/layoutlib/create/tests/data/mock_android.jar and b/tools/layoutlib/create/tests/data/mock_android.jar differ
diff --git a/tools/layoutlib/create/tests/data/mock_android.jardesc b/tools/layoutlib/create/tests/data/mock_android.jardesc
deleted file mode 100644
index 95f7591d7046a..0000000000000
--- a/tools/layoutlib/create/tests/data/mock_android.jardesc
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/tools/layoutlib/create/tests/mock_data/java/lang/JavaClass.java b/tools/layoutlib/create/tests/mock_data/java/lang/JavaClass.java
new file mode 100644
index 0000000000000..59612e95d354c
--- /dev/null
+++ b/tools/layoutlib/create/tests/mock_data/java/lang/JavaClass.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
+ *
+ * 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 java.lang;
+
+public class JavaClass {
+
+ public static String test = "test";
+}
diff --git a/tools/layoutlib/create/tests/mock_android/dummy/InnerTest.java b/tools/layoutlib/create/tests/mock_data/mock_android/dummy/InnerTest.java
similarity index 89%
rename from tools/layoutlib/create/tests/mock_android/dummy/InnerTest.java
rename to tools/layoutlib/create/tests/mock_data/mock_android/dummy/InnerTest.java
index e355ead16a7d2..d3a1d05b98fd9 100644
--- a/tools/layoutlib/create/tests/mock_android/dummy/InnerTest.java
+++ b/tools/layoutlib/create/tests/mock_data/mock_android/dummy/InnerTest.java
@@ -19,6 +19,7 @@ package mock_android.dummy;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
+import java.util.List;
public class InnerTest {
@@ -66,13 +67,13 @@ public class InnerTest {
}
}
- public void genericMethod1(X a, X[] a) {
+ public void genericMethod1(X a, X[] b) {
}
public void genericMethod2(X a, List b) {
}
- public void genericMethod3(X a, List b) {
+ public void genericMethod3(X a, List b) {
}
public void genericMethod4(T[] a, Collection b, Collection> c) {
@@ -85,6 +86,6 @@ public class InnerTest {
mInnerInstance = m;
mTheIntEnum = null;
mGeneric1 = new MyGenerics1();
- genericMethod(new DerivingClass[0], new ArrayList(), new ArrayList());
+ genericMethod4(new DerivingClass[0], new ArrayList(), new ArrayList());
}
}
diff --git a/tools/layoutlib/create/tests/mock_android/view/View.java b/tools/layoutlib/create/tests/mock_data/mock_android/view/View.java
similarity index 91%
rename from tools/layoutlib/create/tests/mock_android/view/View.java
rename to tools/layoutlib/create/tests/mock_data/mock_android/view/View.java
index a80a98daf1d47..84ec8a9d90942 100644
--- a/tools/layoutlib/create/tests/mock_android/view/View.java
+++ b/tools/layoutlib/create/tests/mock_data/mock_android/view/View.java
@@ -16,6 +16,10 @@
package mock_android.view;
+import java.lang.JavaClass;
+
public class View {
+ String x = JavaClass.test;
+
}
diff --git a/tools/layoutlib/create/tests/mock_android/view/ViewGroup.java b/tools/layoutlib/create/tests/mock_data/mock_android/view/ViewGroup.java
similarity index 100%
rename from tools/layoutlib/create/tests/mock_android/view/ViewGroup.java
rename to tools/layoutlib/create/tests/mock_data/mock_android/view/ViewGroup.java
diff --git a/tools/layoutlib/create/tests/mock_android/widget/LinearLayout.java b/tools/layoutlib/create/tests/mock_data/mock_android/widget/LinearLayout.java
similarity index 100%
rename from tools/layoutlib/create/tests/mock_android/widget/LinearLayout.java
rename to tools/layoutlib/create/tests/mock_data/mock_android/widget/LinearLayout.java
diff --git a/tools/layoutlib/create/tests/mock_android/widget/TableLayout.java b/tools/layoutlib/create/tests/mock_data/mock_android/widget/TableLayout.java
similarity index 100%
rename from tools/layoutlib/create/tests/mock_android/widget/TableLayout.java
rename to tools/layoutlib/create/tests/mock_data/mock_android/widget/TableLayout.java