aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/tools/android/java/com/google/devtools/build/android
diff options
context:
space:
mode:
authorGravatar Googler <noreply@google.com>2016-07-22 16:33:17 +0000
committerGravatar John Cater <jcater@google.com>2016-07-22 20:11:12 +0000
commitb6a650936b83b12c9efb9014faa403b94a3b5625 (patch)
tree0ad460ce12ffc2650d436ac9651e8ac26eb43e4c /src/tools/android/java/com/google/devtools/build/android
parent238bf4e778a39df24ebebf8be79e205d7a65e1e4 (diff)
Add a resource merger state -> R class writer.
Collects the R class fields from the new merger's internal state, and then writes out either an R.java or R.class. TBD which to use. One concern is that users may want the javadoc. If so, perhaps this could generate the R.class, and then aapt could generate the srcjar off of the build critical path. Refactor the RClassGenerator to make it easier to use from merger state (vs from R.txt). -- MOS_MIGRATED_REVID=128181306
Diffstat (limited to 'src/tools/android/java/com/google/devtools/build/android')
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/AndroidFrameworkAttrIdJar.java66
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/AndroidFrameworkAttrIdProvider.java36
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/AndroidResourceClassWriter.java351
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/AndroidResourceProcessor.java12
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/DataResource.java9
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/DataResourceXml.java7
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/DataValueFile.java7
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/FullyQualifiedName.java4
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/UnwrittenMergedAndroidData.java19
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/XmlResourceValue.java12
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/resources/FieldInitializer.java47
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/resources/IntArrayFieldInitializer.java100
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/resources/IntFieldInitializer.java60
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/resources/RClassGenerator.java359
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/xml/ArrayXmlResourceValue.java7
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/xml/AttrXmlResourceValue.java62
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/xml/IdXmlResourceValue.java7
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/xml/PluralXmlResourceValue.java7
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/xml/SimpleXmlResourceValue.java7
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/xml/StyleXmlResourceValue.java7
-rw-r--r--src/tools/android/java/com/google/devtools/build/android/xml/StyleableXmlResourceValue.java7
21 files changed, 992 insertions, 201 deletions
diff --git a/src/tools/android/java/com/google/devtools/build/android/AndroidFrameworkAttrIdJar.java b/src/tools/android/java/com/google/devtools/build/android/AndroidFrameworkAttrIdJar.java
new file mode 100644
index 0000000000..eb41f0d544
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/AndroidFrameworkAttrIdJar.java
@@ -0,0 +1,66 @@
+// Copyright 2016 The Bazel Authors. All rights reserved.
+//
+// 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.google.devtools.build.android;
+
+import java.lang.reflect.Field;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Loads android.R.attr resource IDs from an android jar. An alternative may be to parse the
+ * res/values/public.xml from the SDK, but the class loading approach is ~20ms, right now.
+ */
+public class AndroidFrameworkAttrIdJar implements AndroidFrameworkAttrIdProvider {
+
+ private static final String ANDROID_ATTR_CLASS = "android.R$attr";
+ private final Path androidJar;
+ private Map<String, Integer> cachedFields;
+
+ public AndroidFrameworkAttrIdJar(Path androidJar) {
+ this.androidJar = androidJar;
+ }
+
+ @Override
+ public int getAttrId(String fieldName) throws AttrLookupException {
+ // Lazily load the ANDROID_ATTR_CLASS from the androidJar, to save time if never end up
+ // needing the android framework attributes. This provider can only work for one given
+ // androidJar path, since we never invalidate the lazily filled cache.
+ if (cachedFields == null) {
+ cachedFields = getAttrFields();
+ }
+ Integer result = cachedFields.get(fieldName);
+ if (result == null) {
+ throw new AttrLookupException("Android attribute not found: " + fieldName);
+ }
+ return result;
+ }
+
+ private Map<String, Integer> getAttrFields() throws AttrLookupException {
+ try {
+ URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{androidJar.toUri().toURL()});
+ Class<?> attrClass = urlClassLoader.loadClass(ANDROID_ATTR_CLASS);
+ Map<String, Integer> attributeIds = new HashMap<>();
+ for (Field field : attrClass.getFields()) {
+ attributeIds.put(field.getName(), field.getInt(null));
+ }
+ return attributeIds;
+ } catch (ClassNotFoundException | IllegalAccessException | MalformedURLException e) {
+ throw new AttrLookupException(e);
+ }
+ }
+}
diff --git a/src/tools/android/java/com/google/devtools/build/android/AndroidFrameworkAttrIdProvider.java b/src/tools/android/java/com/google/devtools/build/android/AndroidFrameworkAttrIdProvider.java
new file mode 100644
index 0000000000..522dca5974
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/AndroidFrameworkAttrIdProvider.java
@@ -0,0 +1,36 @@
+// Copyright 2016 The Bazel Authors. All rights reserved.
+//
+// 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.google.devtools.build.android;
+
+/**
+ * Provides the android.R.attr field IDs.
+ */
+public interface AndroidFrameworkAttrIdProvider {
+
+ int getAttrId(String attrName) throws AttrLookupException;
+
+ /**
+ * Exception thrown when lookup fails.
+ */
+ final class AttrLookupException extends Exception {
+
+ AttrLookupException(Throwable t) {
+ super(t);
+ }
+
+ AttrLookupException(String s) {
+ super(s);
+ }
+ }
+}
diff --git a/src/tools/android/java/com/google/devtools/build/android/AndroidResourceClassWriter.java b/src/tools/android/java/com/google/devtools/build/android/AndroidResourceClassWriter.java
new file mode 100644
index 0000000000..0d51f50d2e
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/AndroidResourceClassWriter.java
@@ -0,0 +1,351 @@
+// Copyright 2016 The Bazel Authors. All rights reserved.
+//
+// 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.google.devtools.build.android;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.android.SdkConstants;
+import com.android.resources.ResourceType;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Ordering;
+import com.google.devtools.build.android.AndroidFrameworkAttrIdProvider.AttrLookupException;
+import com.google.devtools.build.android.resources.FieldInitializer;
+import com.google.devtools.build.android.resources.IntArrayFieldInitializer;
+import com.google.devtools.build.android.resources.IntFieldInitializer;
+import com.google.devtools.build.android.resources.RClassGenerator;
+import java.io.BufferedWriter;
+import java.io.Flushable;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.EnumMap;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+/**
+ * Generates the R class for an android_library with made up field initializers for the ids. The
+ * real ids will be assigned when we build the android_binary.
+ *
+ * Collects the R class fields from the merged resource maps, and then writes out the resource class
+ * files.
+ */
+public class AndroidResourceClassWriter implements Flushable {
+
+ private final AndroidFrameworkAttrIdProvider androidIdProvider;
+ private final Path outputBasePath;
+ private final String packageName;
+
+ private final Map<ResourceType, Set<String>> innerClasses = new EnumMap<>(ResourceType.class);
+ private final Map<String, Map<String, Boolean>> styleableAttrs = new HashMap<>();
+
+ private static final String NORMALIZED_ANDROID_PREFIX = "android_";
+
+ public AndroidResourceClassWriter(
+ AndroidFrameworkAttrIdProvider androidIdProvider,
+ Path outputBasePath,
+ String packageName) {
+ this.androidIdProvider = androidIdProvider;
+ this.outputBasePath = outputBasePath;
+ this.packageName = packageName;
+ }
+
+ public void writeSimpleResource(ResourceType type, String name) {
+ Set<String> fields = innerClasses.get(type);
+ if (fields == null) {
+ fields = new HashSet<>();
+ innerClasses.put(type, fields);
+ }
+ fields.add(normalizeName(name));
+ }
+
+ public void writeStyleableResource(FullyQualifiedName key,
+ Map<FullyQualifiedName, Boolean> attrs) {
+ ResourceType type = ResourceType.STYLEABLE;
+ // The configuration can play a role in sorting, but that isn't modeled yet.
+ String normalizedStyleableName = normalizeName(key.name());
+ writeSimpleResource(type, normalizedStyleableName);
+ // We should have merged styleables, so there should only be one definition per configuration.
+ // However, we don't combine across configurations, so there can be a pre-existing definition.
+ Map<String, Boolean> normalizedAttrs = styleableAttrs.get(normalizedStyleableName);
+ if (normalizedAttrs == null) {
+ // We need to maintain the original order of the attrs.
+ normalizedAttrs = new LinkedHashMap<>();
+ styleableAttrs.put(normalizedStyleableName, normalizedAttrs);
+ }
+ for (Map.Entry<FullyQualifiedName, Boolean> attrEntry : attrs.entrySet()) {
+ String normalizedAttrName = normalizeAttrName(attrEntry.getKey().name());
+ normalizedAttrs.put(normalizedAttrName, attrEntry.getValue());
+ }
+ }
+
+ @Override
+ public void flush() throws IOException {
+ Map<ResourceType, List<FieldInitializer>> initializers = new EnumMap<>(ResourceType.class);
+ try {
+ fillInitializers(initializers);
+ } catch (AttrLookupException e) {
+ throw new IOException(e);
+ }
+
+ writeAsJava(initializers);
+ writeAsClass(initializers);
+ }
+
+ /**
+ * Determine the TT portion of the resource ID (PPTTEEEE) that aapt would have assigned. This not
+ * at all alphabetical. It depends on the order in which the types are processed, and whether or
+ * not previous types are present (compact). See the code in aapt Resource.cpp:buildResources().
+ * There are several seemingly arbitrary and different processing orders in the function, but the
+ * ordering is determined specifically by the portion at: <a href="https://android.googlesource.com/platform/frameworks/base.git/+/marshmallow-release/tools/aapt/Resource.cpp#1254">
+ * Resource.cpp:buildResources() </a>
+ *
+ * where it does:
+ * <pre>
+ * if (drawables != NULL) { ... }
+ * if (mipmaps != NULL) { ... }
+ * if (layouts != NULL) { ... }
+ * </pre>
+ *
+ * Numbering starts at 1 instead of 0, and ResourceType.ATTR comes before the rest.
+ * ResourceType.STYLEABLE doesn't actually need a resource ID, so that is skipped. We encode the
+ * ordering in the following list.
+ */
+ private static final List<ResourceType> AAPT_TYPE_ORDERING = ImmutableList.of(
+ ResourceType.DRAWABLE,
+ ResourceType.MIPMAP,
+ ResourceType.LAYOUT,
+ ResourceType.ANIM,
+ ResourceType.ANIMATOR,
+ ResourceType.TRANSITION,
+ ResourceType.INTERPOLATOR,
+ ResourceType.XML,
+ ResourceType.RAW,
+ // Begin VALUES portion
+ // Technically, aapt just assigns according to declaration order in the source value.xml files
+ // so it isn't really deterministic. However, the Gradle merger sorts the values.xml file
+ // before invoking aapt, so assume that is also done.
+ ResourceType.ARRAY,
+ ResourceType.BOOL,
+ ResourceType.COLOR,
+ ResourceType.DIMEN,
+ ResourceType.FRACTION,
+ ResourceType.ID,
+ ResourceType.INTEGER,
+ ResourceType.PLURALS,
+ ResourceType.STRING,
+ ResourceType.STYLE,
+ // End VALUES portion
+ // Technically, file-based COLOR resources come next. If we care about complete equivalence
+ // we should separate the file-based resources from value-based resources so that we can
+ // number them the same way.
+ ResourceType.MENU
+ );
+
+ private Map<ResourceType, Integer> chooseTypeIds() {
+ Map<ResourceType, Integer> allocatedTypeIds = new EnumMap<>(ResourceType.class);
+ // ATTR always takes up slot #1, even if it isn't present.
+ allocatedTypeIds.put(ResourceType.ATTR, 1);
+ // The rest are packed starting at #2.
+ int nextTypeId = 2;
+ for (ResourceType t : AAPT_TYPE_ORDERING) {
+ if (innerClasses.containsKey(t)) {
+ allocatedTypeIds.put(t, nextTypeId);
+ ++nextTypeId;
+ }
+ }
+ // Sanity check that everything has been assigned, except STYLEABLE.
+ // We will need to update the list if there is a new resource type.
+ for (ResourceType t : innerClasses.keySet()) {
+ Preconditions.checkArgument(t == ResourceType.STYLEABLE || allocatedTypeIds.containsKey(t));
+ }
+ return allocatedTypeIds;
+ }
+
+ private Map<String, Integer> assignAttrIds(int attrTypeId) {
+ // Attrs are special, since they can be defined within a declare-styleable. Those are sorted
+ // after top-level definitions.
+ ImmutableMap.Builder<String, Integer> attrToIdBuilder = ImmutableMap.builder();
+ if (!innerClasses.containsKey(ResourceType.ATTR)) {
+ return attrToIdBuilder.build();
+ }
+ Set<String> inlineAttrs = new HashSet<>();
+ Set<String> styleablesWithInlineAttrs = new TreeSet<>();
+ for (Map.Entry<String, Map<String, Boolean>> styleableAttrEntry
+ : styleableAttrs.entrySet()) {
+ Map<String, Boolean> attrs = styleableAttrEntry.getValue();
+ for (Map.Entry<String, Boolean> attrEntry : attrs.entrySet()) {
+ if (attrEntry.getValue()) {
+ inlineAttrs.add(attrEntry.getKey());
+ styleablesWithInlineAttrs.add(styleableAttrEntry.getKey());
+ }
+ }
+ }
+ int nextId = 0x7f000000 | (attrTypeId << 16);
+ // Technically, aapt assigns based on declaration order, but the merge should have sorted
+ // the non-inline attributes, so assigning by sorted order is the same.
+ ImmutableList<String> sortedAttrs = Ordering.natural()
+ .immutableSortedCopy(innerClasses.get(ResourceType.ATTR));
+ for (String attr : sortedAttrs) {
+ if (!inlineAttrs.contains(attr)) {
+ attrToIdBuilder.put(attr, nextId);
+ ++nextId;
+ }
+ }
+ for (String styleable : styleablesWithInlineAttrs) {
+ Map<String, Boolean> attrs = styleableAttrs.get(styleable);
+ for (Map.Entry<String, Boolean> attrEntry : attrs.entrySet()) {
+ if (attrEntry.getValue()) {
+ attrToIdBuilder.put(attrEntry.getKey(), nextId);
+ ++nextId;
+ }
+ }
+ }
+ return attrToIdBuilder.build();
+ }
+
+ private void fillInitializers(Map<ResourceType, List<FieldInitializer>> initializers)
+ throws AttrLookupException {
+ Map<ResourceType, Integer> typeIdMap = chooseTypeIds();
+ Map<String, Integer> attrAssignments = assignAttrIds(typeIdMap.get(ResourceType.ATTR));
+ for (Map.Entry<ResourceType, Set<String>> fieldEntries : innerClasses.entrySet()) {
+ ResourceType type = fieldEntries.getKey();
+ ImmutableList<String> sortedFields = Ordering
+ .natural()
+ .immutableSortedCopy(fieldEntries.getValue());
+ List<FieldInitializer> fields;
+ if (type == ResourceType.STYLEABLE) {
+ fields = getStyleableInitializers(attrAssignments, sortedFields);
+ } else if (type == ResourceType.ATTR) {
+ fields = getAttrInitializers(attrAssignments, sortedFields);
+ } else {
+ int typeId = typeIdMap.get(type);
+ fields = getResourceInitializers(typeId, sortedFields);
+ }
+ // The maximum number of Java fields is 2^16.
+ // See the JVM reference "4.11. Limitations of the Java Virtual Machine."
+ Preconditions.checkArgument(fields.size() < (1 << 16));
+ initializers.put(type, fields);
+ }
+ }
+
+ private List<FieldInitializer> getStyleableInitializers(
+ Map<String, Integer> attrAssignments,
+ Collection<String> styleableFields)
+ throws AttrLookupException {
+ ImmutableList.Builder<FieldInitializer> initList = ImmutableList.builder();
+ for (String field : styleableFields) {
+ Set<String> attrs = styleableAttrs.get(field).keySet();
+ ImmutableMap.Builder<String, Integer> arrayInitValues = ImmutableMap.builder();
+ for (String attr : attrs) {
+ Integer attrId = attrAssignments.get(attr);
+ if (attrId == null) {
+ // It should be a framework resource, otherwise we don't know about the resource.
+ if (!attr.startsWith(NORMALIZED_ANDROID_PREFIX)) {
+ throw new AttrLookupException("App attribute not found: " + attr);
+ }
+ String attrWithoutPrefix = attr.substring(NORMALIZED_ANDROID_PREFIX.length());
+ attrId = androidIdProvider.getAttrId(attrWithoutPrefix);
+ }
+ arrayInitValues.put(attr, attrId);
+ }
+ // The styleable array should be sorted by ID value.
+ // Make sure that if we have android: framework attributes, their IDs are listed first.
+ ImmutableMap<String, Integer> arrayInitMap = arrayInitValues
+ .orderEntriesByValue(Ordering.<Integer>natural())
+ .build();
+ initList.add(new IntArrayFieldInitializer(field, arrayInitMap.values()));
+ int index = 0;
+ for (String attr : arrayInitMap.keySet()) {
+ initList.add(new IntFieldInitializer(field + "_" + attr, index));
+ ++index;
+ }
+ }
+ return initList.build();
+ }
+
+ private List<FieldInitializer> getAttrInitializers(
+ Map<String, Integer> attrAssignments,
+ Collection<String> fields) {
+ ImmutableList.Builder<FieldInitializer> initList = ImmutableList.builder();
+ for (String field : fields) {
+ int attrId = attrAssignments.get(field);
+ initList.add(new IntFieldInitializer(field, attrId));
+ }
+ return initList.build();
+ }
+
+ private List<FieldInitializer> getResourceInitializers(
+ int typeId,
+ Collection<String> fields) {
+ ImmutableList.Builder<FieldInitializer> initList = ImmutableList.builder();
+ int resourceIds = 0x7f000000 | typeId << 16;
+ for (String field : fields) {
+ initList.add(new IntFieldInitializer(field, resourceIds));
+ ++resourceIds;
+ }
+ return initList.build();
+ }
+
+ private void writeAsJava(Map<ResourceType, List<FieldInitializer>> initializers)
+ throws IOException {
+ String packageDir = packageName.replace('.', '/');
+ Path packagePath = outputBasePath.resolve(packageDir);
+ Path rJavaPath = packagePath.resolve(SdkConstants.FN_RESOURCE_CLASS);
+ Files.createDirectories(rJavaPath.getParent());
+ try (BufferedWriter writer = Files.newBufferedWriter(rJavaPath, UTF_8)) {
+ writer.write("/* AUTO-GENERATED FILE. DO NOT MODIFY.\n");
+ writer.write(" *\n");
+ writer.write(" * This class was automatically generated by the\n");
+ writer.write(" * bazel tool from the resource data it found. It\n");
+ writer.write(" * should not be modified by hand.\n");
+ writer.write(" */\n");
+ writer.write(String.format("package %s;\n", packageName));
+ writer.write("public final class R {\n");
+ for (Map.Entry<ResourceType, Set<String>> fieldEntries : innerClasses.entrySet()) {
+ ResourceType type = fieldEntries.getKey();
+ writer.write(String.format(" public static final class %s {\n", type.getName()));
+ for (FieldInitializer field : initializers.get(type)) {
+ field.writeInitSource(writer);
+ }
+ writer.write(" }\n");
+ }
+ writer.write("}");
+ }
+ }
+
+ private void writeAsClass(Map<ResourceType, List<FieldInitializer>> initializers)
+ throws IOException {
+ RClassGenerator rClassGenerator =
+ new RClassGenerator(outputBasePath, packageName, initializers, false /* finalFields */);
+ rClassGenerator.write();
+ }
+
+ private static String normalizeName(String resourceName) {
+ return resourceName.replace('.', '_');
+ }
+
+ private static String normalizeAttrName(String attrName) {
+ // In addition to ".", attributes can have ":", e.g., for "android:textColor".
+ return normalizeName(attrName).replace(':', '_');
+ }
+
+}
diff --git a/src/tools/android/java/com/google/devtools/build/android/AndroidResourceProcessor.java b/src/tools/android/java/com/google/devtools/build/android/AndroidResourceProcessor.java
index 55ef25e759..53a7057fc6 100644
--- a/src/tools/android/java/com/google/devtools/build/android/AndroidResourceProcessor.java
+++ b/src/tools/android/java/com/google/devtools/build/android/AndroidResourceProcessor.java
@@ -687,18 +687,14 @@ public class AndroidResourceProcessor {
boolean finalFields) throws IOException {
for (String packageName : libMap.keySet()) {
Collection<SymbolLoader> symbols = libMap.get(packageName);
- RClassGenerator classWriter =
- new RClassGenerator(classesOut.toFile(), packageName, fullSymbolValues, finalFields);
- for (SymbolLoader symbolLoader : symbols) {
- classWriter.addSymbolsToWrite(symbolLoader);
- }
+ RClassGenerator classWriter = RClassGenerator.fromSymbols(
+ classesOut, packageName, fullSymbolValues, symbols, finalFields);
classWriter.write();
}
// Unlike the R.java generation, we also write the app's R.class file so that the class
// jar file can be complete (aapt doesn't generate it for us).
- RClassGenerator classWriter =
- new RClassGenerator(classesOut.toFile(), appPackageName, fullSymbolValues, finalFields);
- classWriter.addSymbolsToWrite(fullSymbolValues);
+ RClassGenerator classWriter = RClassGenerator.fromSymbols(classesOut, appPackageName,
+ fullSymbolValues, ImmutableList.of(fullSymbolValues), finalFields);
classWriter.write();
}
diff --git a/src/tools/android/java/com/google/devtools/build/android/DataResource.java b/src/tools/android/java/com/google/devtools/build/android/DataResource.java
index 44d4a9578c..d1836b8a13 100644
--- a/src/tools/android/java/com/google/devtools/build/android/DataResource.java
+++ b/src/tools/android/java/com/google/devtools/build/android/DataResource.java
@@ -22,7 +22,7 @@ import java.io.IOException;
*/
public interface DataResource extends DataValue {
/**
- * Write as a resource using the supplied {@link MergeDataWriter}.
+ * Write as a resource using the supplied {@link AndroidDataWritingVisitor}.
*/
void writeResource(FullyQualifiedName key, AndroidDataWritingVisitor mergedDataWriter)
throws IOException, MergingException;
@@ -35,4 +35,11 @@ public interface DataResource extends DataValue {
* @throws IllegalArgumentException if either resource cannot combine with the other.
*/
DataResource combineWith(DataResource resource);
+
+ /**
+ * Queue up writing the resource to the given {@link AndroidResourceClassWriter}.
+ */
+ void writeResourceToClass(
+ FullyQualifiedName key,
+ AndroidResourceClassWriter resourceClassWriter);
}
diff --git a/src/tools/android/java/com/google/devtools/build/android/DataResourceXml.java b/src/tools/android/java/com/google/devtools/build/android/DataResourceXml.java
index f8b9982a0f..89ebc0018f 100644
--- a/src/tools/android/java/com/google/devtools/build/android/DataResourceXml.java
+++ b/src/tools/android/java/com/google/devtools/build/android/DataResourceXml.java
@@ -279,6 +279,13 @@ public class DataResourceXml implements DataResource {
}
@Override
+ public void writeResourceToClass(
+ FullyQualifiedName key,
+ AndroidResourceClassWriter resourceClassWriter) {
+ xml.writeResourceToClass(key, resourceClassWriter);
+ }
+
+ @Override
public int serializeTo(DataKey key, OutputStream outStream) throws IOException {
return xml.serializeTo(source, namespaces, outStream);
}
diff --git a/src/tools/android/java/com/google/devtools/build/android/DataValueFile.java b/src/tools/android/java/com/google/devtools/build/android/DataValueFile.java
index 2f41931d32..e25ab44c12 100644
--- a/src/tools/android/java/com/google/devtools/build/android/DataValueFile.java
+++ b/src/tools/android/java/com/google/devtools/build/android/DataValueFile.java
@@ -100,4 +100,11 @@ public class DataValueFile implements DataResource, DataAsset {
public DataResource combineWith(DataResource resource) {
throw new IllegalArgumentException(getClass() + " does not combine.");
}
+
+ @Override
+ public void writeResourceToClass(FullyQualifiedName key,
+ AndroidResourceClassWriter resourceClassWriter) {
+ resourceClassWriter.writeSimpleResource(key.type(), key.name());
+ }
+
}
diff --git a/src/tools/android/java/com/google/devtools/build/android/FullyQualifiedName.java b/src/tools/android/java/com/google/devtools/build/android/FullyQualifiedName.java
index bf4e0817d1..4f953c8bd4 100644
--- a/src/tools/android/java/com/google/devtools/build/android/FullyQualifiedName.java
+++ b/src/tools/android/java/com/google/devtools/build/android/FullyQualifiedName.java
@@ -388,6 +388,10 @@ public class FullyQualifiedName implements DataKey, Comparable<FullyQualifiedNam
return resourceName;
}
+ public ResourceType type() {
+ return resourceType;
+ }
+
private FullyQualifiedName(
String pkg,
ImmutableList<String> qualifiers,
diff --git a/src/tools/android/java/com/google/devtools/build/android/UnwrittenMergedAndroidData.java b/src/tools/android/java/com/google/devtools/build/android/UnwrittenMergedAndroidData.java
index 2eddb3e1c9..cefd4454cd 100644
--- a/src/tools/android/java/com/google/devtools/build/android/UnwrittenMergedAndroidData.java
+++ b/src/tools/android/java/com/google/devtools/build/android/UnwrittenMergedAndroidData.java
@@ -85,6 +85,25 @@ public class UnwrittenMergedAndroidData {
}
}
+ public void writeResourceClass(AndroidResourceClassWriter resourceClassWriter)
+ throws IOException {
+ writeResourceClassItems(primary, resourceClassWriter);
+ writeResourceClassItems(transitive, resourceClassWriter);
+ resourceClassWriter.flush();
+ }
+
+ private void writeResourceClassItems(
+ ParsedAndroidData resources, AndroidResourceClassWriter resourceClassWriter)
+ throws IOException {
+ for (Entry<DataKey, DataResource> entry : resources.iterateDataResourceEntries()) {
+ // TODO(corysmith): Resolve the nit of casting to a FullyQualifiedName by sorting
+ // out the type structure and generics of DataKey, ParsedAndroidData, AndroidDataMerger and
+ // MergeConflict.
+ entry.getValue()
+ .writeResourceToClass((FullyQualifiedName) entry.getKey(), resourceClassWriter);
+ }
+ }
+
@Override
public String toString() {
return MoreObjects.toStringHelper(this)
diff --git a/src/tools/android/java/com/google/devtools/build/android/XmlResourceValue.java b/src/tools/android/java/com/google/devtools/build/android/XmlResourceValue.java
index 0eb91be1f5..88be7e528c 100644
--- a/src/tools/android/java/com/google/devtools/build/android/XmlResourceValue.java
+++ b/src/tools/android/java/com/google/devtools/build/android/XmlResourceValue.java
@@ -42,4 +42,16 @@ public interface XmlResourceValue {
* @throws IllegalArgumentException if either value cannot combine with the other.
*/
XmlResourceValue combineWith(XmlResourceValue value);
+
+ /**
+ * Queue up writing the resource to the given {@link AndroidResourceClassWriter}.
+ * Each resource can generate one or more (in the case of styleable) fields and inner classes
+ * in the R class.
+ *
+ * @param key The FullyQualifiedName of the resource
+ * @param resourceClassWriter the R java class writer
+ */
+ void writeResourceToClass(
+ FullyQualifiedName key,
+ AndroidResourceClassWriter resourceClassWriter);
}
diff --git a/src/tools/android/java/com/google/devtools/build/android/resources/FieldInitializer.java b/src/tools/android/java/com/google/devtools/build/android/resources/FieldInitializer.java
new file mode 100644
index 0000000000..7f1672dfe5
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/resources/FieldInitializer.java
@@ -0,0 +1,47 @@
+// Copyright 2016 The Bazel Authors. All rights reserved.
+//
+// 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.google.devtools.build.android.resources;
+
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.commons.InstructionAdapter;
+
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ * Represents a field and its initializer (where initialization is either part of the field
+ * definition, or done via code in the static clinit function).
+ */
+public interface FieldInitializer {
+ /**
+ * Write the bytecode for the field definition.
+ *
+ * @return true if the initializer is deferred to clinit code.
+ */
+ boolean writeFieldDefinition(ClassWriter cw, int accessLevel, boolean isFinal);
+
+ /**
+ * Write the bytecode for the clinit portion of initializer.
+ *
+ * @return the number of stack slots needed for the code.
+ */
+ int writeCLInit(InstructionAdapter insts, String className);
+
+ /**
+ * Write the source code for the initializer to the given writer.
+ * Unlike {@link #writeFieldDefinition}, this assumes non-final fields, since we don't use this
+ * for final fields yet.
+ */
+ void writeInitSource(Writer writer) throws IOException;
+}
diff --git a/src/tools/android/java/com/google/devtools/build/android/resources/IntArrayFieldInitializer.java b/src/tools/android/java/com/google/devtools/build/android/resources/IntArrayFieldInitializer.java
new file mode 100644
index 0000000000..4d37a5ae0e
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/resources/IntArrayFieldInitializer.java
@@ -0,0 +1,100 @@
+// Copyright 2016 The Bazel Authors. All rights reserved.
+//
+// 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.google.devtools.build.android.resources;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableCollection;
+import com.google.common.collect.ImmutableList;
+
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.commons.InstructionAdapter;
+
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ * Models an int[] field initializer.
+ */
+public final class IntArrayFieldInitializer implements FieldInitializer {
+
+ public static final String DESC = "[I";
+ private final String fieldName;
+ private final ImmutableCollection<Integer> values;
+
+ public IntArrayFieldInitializer(String fieldName, ImmutableCollection<Integer> values) {
+ this.fieldName = fieldName;
+ this.values = values;
+ }
+
+ public static FieldInitializer of(String name, String value) {
+ Preconditions.checkArgument(value.startsWith("{ "), "Expected list starting with { ");
+ Preconditions.checkArgument(value.endsWith(" }"), "Expected list ending with } ");
+ // Check for an empty list, which is "{ }".
+ if (value.length() < 4) {
+ return new IntArrayFieldInitializer(name, ImmutableList.<Integer>of());
+ }
+ ImmutableList.Builder<Integer> intValues = ImmutableList.builder();
+ String trimmedValue = value.substring(2, value.length() - 2);
+ Iterable<String> valueStrings = Splitter.on(',')
+ .trimResults()
+ .split(trimmedValue);
+ for (String valueString : valueStrings) {
+ intValues.add(Integer.decode(valueString));
+ }
+ return new IntArrayFieldInitializer(name, intValues.build());
+ }
+
+ @Override
+ public boolean writeFieldDefinition(ClassWriter cw, int accessLevel, boolean isFinal) {
+ cw.visitField(accessLevel, fieldName, DESC, null, null)
+ .visitEnd();
+ return true;
+ }
+
+ @Override
+ public int writeCLInit(InstructionAdapter insts, String className) {
+ insts.iconst(values.size());
+ insts.newarray(Type.INT_TYPE);
+ int curIndex = 0;
+ for (Integer value : values) {
+ insts.dup();
+ insts.iconst(curIndex);
+ insts.iconst(value);
+ insts.astore(Type.INT_TYPE);
+ ++curIndex;
+ }
+ insts.putstatic(className, fieldName, DESC);
+ // Needs up to 4 stack slots for: the array ref for the putstatic, the dup of the array ref
+ // for the store, the index, and the value to store.
+ return 4;
+ }
+
+ @Override
+ public void writeInitSource(Writer writer) throws IOException {
+ StringBuilder builder = new StringBuilder();
+ boolean first = true;
+ for (Integer attrId : values) {
+ if (first) {
+ first = false;
+ builder.append(String.format("0x%x", attrId));
+ } else {
+ builder.append(String.format(", 0x%x", attrId));
+ }
+ }
+ writer.write(String.format(" public static int[] %s = { %s };\n",
+ fieldName, builder.toString()));
+ }
+}
diff --git a/src/tools/android/java/com/google/devtools/build/android/resources/IntFieldInitializer.java b/src/tools/android/java/com/google/devtools/build/android/resources/IntFieldInitializer.java
new file mode 100644
index 0000000000..cf7c9f454a
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/resources/IntFieldInitializer.java
@@ -0,0 +1,60 @@
+// Copyright 2016 The Bazel Authors. All rights reserved.
+//
+// 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.google.devtools.build.android.resources;
+
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.commons.InstructionAdapter;
+
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ * Models an int field initializer.
+ */
+public final class IntFieldInitializer implements FieldInitializer {
+
+ private final String fieldName;
+ private final int value;
+ private static final String DESC = "I";
+
+ public IntFieldInitializer(String fieldName, int value) {
+ this.fieldName = fieldName;
+ this.value = value;
+ }
+
+ public static FieldInitializer of(String name, String value) {
+ return new IntFieldInitializer(name, Integer.decode(value));
+ }
+
+ @Override
+ public boolean writeFieldDefinition(ClassWriter cw, int accessLevel, boolean isFinal) {
+ cw.visitField(accessLevel, fieldName, DESC, null, isFinal ? value : null)
+ .visitEnd();
+ return !isFinal;
+ }
+
+ @Override
+ public int writeCLInit(InstructionAdapter insts, String className) {
+ insts.iconst(value);
+ insts.putstatic(className, fieldName, DESC);
+ // Just needs one stack slot for the iconst.
+ return 1;
+ }
+
+ @Override
+ public void writeInitSource(Writer writer) throws IOException {
+ writer.write(String.format(" public static int %s = 0x%x;\n",
+ fieldName, value));
+ }
+}
diff --git a/src/tools/android/java/com/google/devtools/build/android/resources/RClassGenerator.java b/src/tools/android/java/com/google/devtools/build/android/resources/RClassGenerator.java
index 8735e9ba8c..2ae68f11ea 100644
--- a/src/tools/android/java/com/google/devtools/build/android/resources/RClassGenerator.java
+++ b/src/tools/android/java/com/google/devtools/build/android/resources/RClassGenerator.java
@@ -13,31 +13,30 @@
// limitations under the License.
package com.google.devtools.build.android.resources;
+import com.android.SdkConstants;
+import com.android.builder.internal.SymbolLoader;
+import com.android.builder.internal.SymbolLoader.SymbolEntry;
+import com.android.resources.ResourceType;
import com.google.common.base.Preconditions;
import com.google.common.base.Splitter;
import com.google.common.collect.HashBasedTable;
-import com.google.common.collect.ImmutableList;
import com.google.common.collect.Table;
-import com.google.common.io.Files;
-
-import com.android.SdkConstants;
-import com.android.builder.internal.SymbolLoader;
-import com.android.builder.internal.SymbolLoader.SymbolEntry;
-
-import org.objectweb.asm.ClassWriter;
-import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Opcodes;
-import org.objectweb.asm.Type;
-import org.objectweb.asm.commons.InstructionAdapter;
-
-import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
+import java.nio.file.Files;
+import java.nio.file.Path;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
+import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.commons.InstructionAdapter;
/**
* Writes out bytecode for an R.class directly, rather than go through an R.java and compile. This
@@ -49,49 +48,74 @@ public class RClassGenerator {
private static final int JAVA_VERSION = Opcodes.V1_7;
private static final String SUPER_CLASS = "java/lang/Object";
- private final File outFolder;
+ private final Path outFolder;
private final String packageName;
- private final List<SymbolLoader> symbolTables = new ArrayList<>();
- private final SymbolLoader symbolValues;
+ private final Map<ResourceType, List<FieldInitializer>> initializers;
private final boolean finalFields;
+ private static final Splitter PACKAGE_SPLITTER = Splitter.on('.');
- public RClassGenerator(
- File outFolder,
+ /**
+ * Create an RClassGenerator given the final binary's symbol values, and a collection of symbols
+ * for the given package.
+ *
+ * @param outFolder base folder to place the output R class files.
+ * @param packageName the java package to use for the R class
+ * @param values the final symbol values (may include more symbols than needed for this package)
+ * @param packageSymbols the symbols in this package
+ * @param finalFields true if the fields should be marked final
+ */
+ public static RClassGenerator fromSymbols(
+ Path outFolder,
String packageName,
SymbolLoader values,
+ Collection<SymbolLoader> packageSymbols,
+ boolean finalFields) throws IOException {
+ Table<String, String, SymbolEntry> symbolsTable = getAllSymbols(packageSymbols);
+ Table<String, String, SymbolEntry> valuesTable = getSymbols(values);
+ Map<ResourceType, List<FieldInitializer>> initializers = getInitializers(symbolsTable,
+ valuesTable);
+ return new RClassGenerator(outFolder, packageName, initializers, finalFields);
+ }
+
+ /**
+ * Create an RClassGenerator given a collection of initializers.
+ *
+ * @param outFolder base folder to place the output R class files.
+ * @param packageName the java package to use for the R class
+ * @param initializers the list of initializers to use for each inner class
+ * @param finalFields true if the fields should be marked final
+ */
+ public RClassGenerator(
+ Path outFolder,
+ String packageName,
+ Map<ResourceType, List<FieldInitializer>> initializers,
boolean finalFields) {
this.outFolder = outFolder;
this.packageName = packageName;
- this.symbolValues = values;
this.finalFields = finalFields;
+ this.initializers = initializers;
}
- public void addSymbolsToWrite(SymbolLoader symbols) {
- symbolTables.add(symbols);
- }
-
- private Table<String, String, SymbolEntry> getAllSymbols() throws IOException {
+ private static Table<String, String, SymbolEntry> getAllSymbols(
+ Collection<SymbolLoader> symbolLoaders)
+ throws IOException {
Table<String, String, SymbolEntry> symbols = HashBasedTable.create();
- for (SymbolLoader symbolLoader : symbolTables) {
+ for (SymbolLoader symbolLoader : symbolLoaders) {
symbols.putAll(getSymbols(symbolLoader));
}
return symbols;
}
- private Method symbolsMethod;
- private Table<String, String, SymbolEntry> getSymbols(SymbolLoader symbolLoader)
+ private static Table<String, String, SymbolEntry> getSymbols(SymbolLoader symbolLoader)
throws IOException {
- // TODO(bazel-team): upstream a patch to change the visibility instead of hacking it.
+ // TODO(bazel-team): remove when we update android_ide_common to a version w/ public visibility
try {
- if (symbolsMethod == null) {
- Method getSymbols = SymbolLoader.class.getDeclaredMethod("getSymbols");
- getSymbols.setAccessible(true);
- symbolsMethod = getSymbols;
- }
+ Method getSymbols = SymbolLoader.class.getDeclaredMethod("getSymbols");
+ getSymbols.setAccessible(true);
@SuppressWarnings("unchecked")
Table<String, String, SymbolEntry> result = (Table<String, String, SymbolEntry>)
- symbolsMethod.invoke(symbolLoader);
+ getSymbols.invoke(symbolLoader);
return result;
} catch (ReflectiveOperationException e) {
throw new IOException(e);
@@ -99,202 +123,147 @@ public class RClassGenerator {
}
/**
+ * Convert the {@link SymbolLoader} data, to a map of {@link FieldInitializer}.
+ */
+ private static Map<ResourceType, List<FieldInitializer>> getInitializers(
+ Table<String, String, SymbolEntry> symbols,
+ Table<String, String, SymbolEntry> values) {
+ Map<ResourceType, List<FieldInitializer>> initializers = new EnumMap<>(ResourceType.class);
+ for (String typeName : symbols.rowKeySet()) {
+ ResourceType resourceType = ResourceType.getEnum(typeName);
+ Preconditions.checkNotNull(resourceType);
+ initializers.put(resourceType, getInitializers(typeName, symbols, values));
+ }
+ return initializers;
+ }
+
+ private static List<FieldInitializer> getInitializers(
+ String typeName,
+ Table<String, String, SymbolEntry> symbols,
+ Table<String, String, SymbolEntry> values) {
+ Map<String, SymbolEntry> rowMap = symbols.row(typeName);
+ Set<String> symbolSet = rowMap.keySet();
+ List<String> symbolList = new ArrayList<>(symbolSet);
+ Collections.sort(symbolList);
+ List<FieldInitializer> initializers = new ArrayList<>();
+ for (String symbolName : symbolList) {
+ // get the matching SymbolEntry from the values Table.
+ SymbolEntry value = values.get(typeName, symbolName);
+ Preconditions.checkNotNull(value);
+ if (value.getType().equals("int")) {
+ initializers.add(IntFieldInitializer.of(value.getName(), value.getValue()));
+ } else {
+ Preconditions.checkArgument(value.getType().equals("int[]"));
+ initializers
+ .add(IntArrayFieldInitializer.of(value.getName(), value.getValue()));
+ }
+ }
+ return initializers;
+ }
+
+ /**
* Builds the bytecode and writes out the R.class file, and R$inner.class files.
*/
public void write() throws IOException {
- Splitter splitter = Splitter.on('.');
- Iterable<String> folders = splitter.split(packageName);
- File packageDir = outFolder;
+ Iterable<String> folders = PACKAGE_SPLITTER.split(packageName);
+ Path packageDir = outFolder;
for (String folder : folders) {
- packageDir = new File(packageDir, folder);
+ packageDir = packageDir.resolve(folder);
}
- File rClassFile = new File(packageDir, SdkConstants.FN_COMPILED_RESOURCE_CLASS);
// At least create the outFolder that was requested. However, if there are no symbols, don't
// create the R.class and inner class files (no need to have an empty class).
- Files.createParentDirs(rClassFile);
- Table<String, String, SymbolEntry> symbols = getAllSymbols();
- if (symbols.isEmpty()) {
+ Files.createDirectories(packageDir);
+ if (initializers.isEmpty()) {
return;
}
+ Path rClassFile = packageDir.resolve(SdkConstants.FN_COMPILED_RESOURCE_CLASS);
String packageWithSlashes = packageName.replaceAll("\\.", "/");
String rClassName = packageWithSlashes + "/R";
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
- classWriter
- .visit(JAVA_VERSION, Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL | Opcodes.ACC_SUPER,
- rClassName, null, SUPER_CLASS, null);
+ classWriter.visit(
+ JAVA_VERSION,
+ Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL | Opcodes.ACC_SUPER,
+ rClassName,
+ null, /* signature */
+ SUPER_CLASS,
+ null /* interfaces */);
classWriter.visitSource(SdkConstants.FN_RESOURCE_CLASS, null);
writeConstructor(classWriter);
- Table<String, String, SymbolEntry> values = getSymbols(symbolValues);
- Set<String> rowSet = symbols.rowKeySet();
- List<String> rowList = new ArrayList<>(rowSet);
- Collections.sort(rowList);
-
// Build the R.class w/ the inner classes, then later build the individual R$inner.class.
- for (String row : rowList) {
- String innerClassName = rClassName + "$" + row;
- classWriter.visitInnerClass(innerClassName, rClassName, row,
+ for (ResourceType resourceType : initializers.keySet()) {
+ String innerClassName = rClassName + "$" + resourceType;
+ classWriter.visitInnerClass(
+ innerClassName,
+ rClassName,
+ resourceType.toString(),
Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL | Opcodes.ACC_STATIC);
}
classWriter.visitEnd();
- Files.write(classWriter.toByteArray(), rClassFile);
+ Files.write(rClassFile, classWriter.toByteArray());
// Now generate the R$inner.class files.
- for (String row : rowList) {
- writeInnerClass(symbols, values, packageDir, rClassName, row);
- }
- }
-
- /**
- * Represents an int or int[] field and its initializer (where initialization is done via code in
- * the static clinit function).
- */
- private interface DeferredInitializer {
-
- /**
- * Write the code for the initializer via insts.
- *
- * @return the number of stack slots needed for the code.
- */
- int writeCLInit(String className, InstructionAdapter insts);
- }
-
- private static final class IntArrayDeferredInitializer implements DeferredInitializer {
-
- private final String fieldName;
- private final ImmutableList<Integer> values;
-
- IntArrayDeferredInitializer(String fieldName, ImmutableList<Integer> values) {
- this.fieldName = fieldName;
- this.values = values;
- }
-
- public static DeferredInitializer of(String name, String value) {
- Preconditions.checkArgument(value.startsWith("{ "), "Expected list starting with { ");
- Preconditions.checkArgument(value.endsWith(" }"), "Expected list ending with } ");
- // Check for an empty list, which is "{ }".
- if (value.length() < 4) {
- return new IntArrayDeferredInitializer(name, ImmutableList.<Integer>of());
- }
- ImmutableList.Builder<Integer> intValues = ImmutableList.builder();
- String trimmedValue = value.substring(2, value.length() - 2);
- Iterable<String> valueStrings = Splitter.on(',')
- .trimResults()
- .omitEmptyStrings()
- .split(trimmedValue);
- for (String valueString : valueStrings) {
- intValues.add(Integer.decode(valueString));
- }
- return new IntArrayDeferredInitializer(name, intValues.build());
- }
-
- @Override
- public int writeCLInit(String className, InstructionAdapter insts) {
- insts.iconst(values.size());
- insts.newarray(Type.INT_TYPE);
- int curIndex = 0;
- for (Integer value : values) {
- insts.dup();
- insts.iconst(curIndex);
- insts.iconst(value);
- insts.astore(Type.INT_TYPE);
- ++curIndex;
- }
- insts.putstatic(className, fieldName, "[I");
- // Needs up to 4 stack slots for: the array ref for the putstatic, the dup of the array ref
- // for the store, the index, and the value to store.
- return 4;
- }
- }
-
- private static final class IntDeferredInitializer implements DeferredInitializer {
-
- private final String fieldName;
- private final Integer value;
-
- IntDeferredInitializer(String fieldName, Integer value) {
- this.fieldName = fieldName;
- this.value = value;
- }
-
- public static DeferredInitializer of(String name, String value) {
- return new IntDeferredInitializer(name, Integer.decode(value));
- }
-
- @Override
- public int writeCLInit(String className, InstructionAdapter insts) {
- insts.iconst(value);
- insts.putstatic(className, fieldName, "I");
- // Just needs one stack slot for the iconst.
- return 1;
+ for (Map.Entry<ResourceType, List<FieldInitializer>> entry : initializers.entrySet()) {
+ writeInnerClass(entry.getValue(), packageDir, rClassName, entry.getKey().toString());
}
}
private void writeInnerClass(
- Table<String, String, SymbolEntry> symbols,
- Table<String, String, SymbolEntry> values,
- File packageDir,
+ List<FieldInitializer> initializers,
+ Path packageDir,
String fullyQualifiedOuterClass,
String innerClass) throws IOException {
ClassWriter innerClassWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
- String fullyQualifiedInnerClass = fullyQualifiedOuterClass + "$" + innerClass;
- innerClassWriter
- .visit(JAVA_VERSION, Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL | Opcodes.ACC_SUPER,
- fullyQualifiedInnerClass, null, SUPER_CLASS, null);
- innerClassWriter.visitSource("R.java", null);
- writeConstructor(innerClassWriter);
- innerClassWriter.visitInnerClass(
- fullyQualifiedInnerClass, fullyQualifiedOuterClass, innerClass,
- Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL | Opcodes.ACC_STATIC);
+ String fullyQualifiedInnerClass =
+ writeInnerClassHeader(fullyQualifiedOuterClass, innerClass, innerClassWriter);
- Map<String, SymbolEntry> rowMap = symbols.row(innerClass);
- Set<String> symbolSet = rowMap.keySet();
- List<String> symbolList = new ArrayList<>(symbolSet);
- Collections.sort(symbolList);
- List<DeferredInitializer> deferredInitializers = new ArrayList<>();
+ List<FieldInitializer> deferredInitializers = new ArrayList<>();
int fieldAccessLevel = Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC;
if (finalFields) {
fieldAccessLevel |= Opcodes.ACC_FINAL;
}
- for (String symbolName : symbolList) {
- // get the matching SymbolEntry from the values Table.
- SymbolEntry value = values.get(innerClass, symbolName);
- if (value != null) {
- String desc;
- Object initializer = null;
- if (value.getType().equals("int")) {
- desc = "I";
- if (finalFields) {
- initializer = Integer.decode(value.getValue());
- } else {
- deferredInitializers.add(IntDeferredInitializer.of(value.getName(), value.getValue()));
- }
- } else {
- Preconditions.checkArgument(value.getType().equals("int[]"));
- desc = "[I";
- deferredInitializers
- .add(IntArrayDeferredInitializer.of(value.getName(), value.getValue()));
- }
- innerClassWriter
- .visitField(fieldAccessLevel, value.getName(), desc, null, initializer)
- .visitEnd();
+ for (FieldInitializer init : initializers) {
+ if (init.writeFieldDefinition(innerClassWriter, fieldAccessLevel, finalFields)) {
+ deferredInitializers.add(init);
}
}
-
if (!deferredInitializers.isEmpty()) {
- // build the <clinit> method.
writeStaticClassInit(innerClassWriter, fullyQualifiedInnerClass, deferredInitializers);
}
innerClassWriter.visitEnd();
- File innerFile = new File(packageDir, "R$" + innerClass + ".class");
- Files.write(innerClassWriter.toByteArray(), innerFile);
+ Path innerFile = packageDir.resolve("R$" + innerClass + ".class");
+ Files.write(innerFile, innerClassWriter.toByteArray());
+ }
+
+ private String writeInnerClassHeader(String fullyQualifiedOuterClass, String innerClass,
+ ClassWriter innerClassWriter) {
+ String fullyQualifiedInnerClass = fullyQualifiedOuterClass + "$" + innerClass;
+ innerClassWriter.visit(
+ JAVA_VERSION,
+ Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL | Opcodes.ACC_SUPER,
+ fullyQualifiedInnerClass,
+ null, /* signature */
+ SUPER_CLASS,
+ null /* interfaces */);
+ innerClassWriter.visitSource(SdkConstants.FN_RESOURCE_CLASS, null);
+ writeConstructor(innerClassWriter);
+ innerClassWriter.visitInnerClass(
+ fullyQualifiedInnerClass,
+ fullyQualifiedOuterClass,
+ innerClass,
+ Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL | Opcodes.ACC_STATIC);
+ return fullyQualifiedInnerClass;
}
private static void writeConstructor(ClassWriter classWriter) {
- MethodVisitor constructor = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V",
- null, null);
+ MethodVisitor constructor = classWriter.visitMethod(
+ Opcodes.ACC_PUBLIC,
+ "<init>",
+ "()V",
+ null, /* signature */
+ null /* exceptions */);
constructor.visitCode();
constructor.visitVarInsn(Opcodes.ALOAD, 0);
constructor.visitMethodInsn(Opcodes.INVOKESPECIAL, SUPER_CLASS, "<init>", "()V", false);
@@ -306,14 +275,20 @@ public class RClassGenerator {
private static void writeStaticClassInit(
ClassWriter classWriter,
String className,
- List<DeferredInitializer> deferredInitializers) {
- MethodVisitor visitor = classWriter.visitMethod(Opcodes.ACC_STATIC, "<clinit>", "()V",
- null, null);
+ List<FieldInitializer> initializers) {
+ MethodVisitor visitor = classWriter.visitMethod(
+ Opcodes.ACC_STATIC,
+ "<clinit>",
+ "()V",
+ null, /* signature */
+ null /* exceptions */);
visitor.visitCode();
int stackSlotsNeeded = 0;
InstructionAdapter insts = new InstructionAdapter(visitor);
- for (DeferredInitializer fieldInit : deferredInitializers) {
- stackSlotsNeeded = Math.max(stackSlotsNeeded, fieldInit.writeCLInit(className, insts));
+ for (FieldInitializer fieldInit : initializers) {
+ stackSlotsNeeded = Math.max(
+ stackSlotsNeeded,
+ fieldInit.writeCLInit(insts, className));
}
insts.areturn(Type.VOID_TYPE);
visitor.visitMaxs(stackSlotsNeeded, 0);
diff --git a/src/tools/android/java/com/google/devtools/build/android/xml/ArrayXmlResourceValue.java b/src/tools/android/java/com/google/devtools/build/android/xml/ArrayXmlResourceValue.java
index 71e24691ca..46a382f5bb 100644
--- a/src/tools/android/java/com/google/devtools/build/android/xml/ArrayXmlResourceValue.java
+++ b/src/tools/android/java/com/google/devtools/build/android/xml/ArrayXmlResourceValue.java
@@ -19,6 +19,7 @@ import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.devtools.build.android.AndroidDataWritingVisitor;
import com.google.devtools.build.android.AndroidDataWritingVisitor.ValuesResourceDefinition;
+import com.google.devtools.build.android.AndroidResourceClassWriter;
import com.google.devtools.build.android.FullyQualifiedName;
import com.google.devtools.build.android.XmlResourceValue;
import com.google.devtools.build.android.XmlResourceValues;
@@ -181,6 +182,12 @@ public class ArrayXmlResourceValue implements XmlResourceValue {
throw new IllegalArgumentException(this + " is not a combinable resource.");
}
+ @Override
+ public void writeResourceToClass(FullyQualifiedName key,
+ AndroidResourceClassWriter resourceClassWriter) {
+ resourceClassWriter.writeSimpleResource(key.type(), key.name());
+ }
+
public static XmlResourceValue parseArray(
XMLEventReader eventReader, StartElement start, Namespaces.Collector namespacesCollector)
throws XMLStreamException {
diff --git a/src/tools/android/java/com/google/devtools/build/android/xml/AttrXmlResourceValue.java b/src/tools/android/java/com/google/devtools/build/android/xml/AttrXmlResourceValue.java
index ca7a984bd8..dc8968cb12 100644
--- a/src/tools/android/java/com/google/devtools/build/android/xml/AttrXmlResourceValue.java
+++ b/src/tools/android/java/com/google/devtools/build/android/xml/AttrXmlResourceValue.java
@@ -16,6 +16,7 @@ package com.google.devtools.build.android.xml;
import static com.google.common.base.Predicates.equalTo;
import static com.google.common.base.Predicates.not;
+import com.android.resources.ResourceType;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.MoreObjects;
import com.google.common.base.Preconditions;
@@ -27,6 +28,7 @@ import com.google.common.collect.Ordering;
import com.google.devtools.build.android.AndroidDataWritingVisitor;
import com.google.devtools.build.android.AndroidDataWritingVisitor.StartTag;
import com.google.devtools.build.android.AndroidDataWritingVisitor.ValuesResourceDefinition;
+import com.google.devtools.build.android.AndroidResourceClassWriter;
import com.google.devtools.build.android.FullyQualifiedName;
import com.google.devtools.build.android.XmlResourceValue;
import com.google.devtools.build.android.XmlResourceValues;
@@ -327,6 +329,18 @@ public class AttrXmlResourceValue implements XmlResourceValue {
}
}
+ @Override
+ public void writeResourceToClass(FullyQualifiedName key,
+ AndroidResourceClassWriter resourceClassWriter) {
+ resourceClassWriter.writeSimpleResource(key.type(), key.name());
+ // Flags and enums generate ID fields.
+ if (formats.keySet().contains(FLAGS) || formats.keySet().contains(ENUM)) {
+ for (ResourceXmlAttrValue value : formats.values()) {
+ value.writeToClass(resourceClassWriter);
+ }
+ }
+ }
+
@SuppressWarnings("deprecation")
@Override
public int serializeTo(Path source, Namespaces namespaces, OutputStream output)
@@ -358,6 +372,8 @@ public class AttrXmlResourceValue implements XmlResourceValue {
ValuesResourceDefinition writeTo(ValuesResourceDefinition writer);
SerializeFormat.DataValueXml appendTo(SerializeFormat.DataValueXml.Builder builder);
+
+ void writeToClass(AndroidResourceClassWriter writer);
}
// TODO(corysmith): The ResourceXmlAttrValue implementors, other than enum and flag, share a
@@ -426,6 +442,13 @@ public class AttrXmlResourceValue implements XmlResourceValue {
}
return writer;
}
+
+ @Override
+ public void writeToClass(AndroidResourceClassWriter writer) {
+ for (Map.Entry<String, String> entry : values.entrySet()) {
+ writer.writeSimpleResource(ResourceType.ID, entry.getKey());
+ }
+ }
}
/** Represents an Android Flag Attribute resource. */
@@ -492,6 +515,13 @@ public class AttrXmlResourceValue implements XmlResourceValue {
}
return writer;
}
+
+ @Override
+ public void writeToClass(AndroidResourceClassWriter writer) {
+ for (Map.Entry<String, String> entry : values.entrySet()) {
+ writer.writeSimpleResource(ResourceType.ID, entry.getKey());
+ }
+ }
}
/** Represents an Android Reference Attribute resource. */
@@ -518,6 +548,10 @@ public class AttrXmlResourceValue implements XmlResourceValue {
public ValuesResourceDefinition writeTo(ValuesResourceDefinition writer) {
return writer;
}
+
+ @Override
+ public void writeToClass(AndroidResourceClassWriter writer) {
+ }
}
/** Represents an Android Color Attribute resource. */
@@ -543,6 +577,10 @@ public class AttrXmlResourceValue implements XmlResourceValue {
public ValuesResourceDefinition writeTo(ValuesResourceDefinition writer) {
return writer;
}
+
+ @Override
+ public void writeToClass(AndroidResourceClassWriter writer) {
+ }
}
/** Represents an Android Boolean Attribute resource. */
@@ -568,6 +606,10 @@ public class AttrXmlResourceValue implements XmlResourceValue {
public ValuesResourceDefinition writeTo(ValuesResourceDefinition writer) {
return writer;
}
+
+ @Override
+ public void writeToClass(AndroidResourceClassWriter writer) {
+ }
}
/** Represents an Android Float Attribute resource. */
@@ -593,6 +635,10 @@ public class AttrXmlResourceValue implements XmlResourceValue {
public ValuesResourceDefinition writeTo(ValuesResourceDefinition writer) {
return writer;
}
+
+ @Override
+ public void writeToClass(AndroidResourceClassWriter writer) {
+ }
}
/** Represents an Android Dimension Attribute resource. */
@@ -619,6 +665,10 @@ public class AttrXmlResourceValue implements XmlResourceValue {
public ValuesResourceDefinition writeTo(ValuesResourceDefinition writer) {
return writer;
}
+
+ @Override
+ public void writeToClass(AndroidResourceClassWriter writer) {
+ }
}
/** Represents an Android Integer Attribute resource. */
@@ -644,6 +694,10 @@ public class AttrXmlResourceValue implements XmlResourceValue {
public ValuesResourceDefinition writeTo(ValuesResourceDefinition writer) {
return writer;
}
+
+ @Override
+ public void writeToClass(AndroidResourceClassWriter writer) {
+ }
}
/** Represents an Android String Attribute resource. */
@@ -669,6 +723,10 @@ public class AttrXmlResourceValue implements XmlResourceValue {
public ValuesResourceDefinition writeTo(ValuesResourceDefinition writer) {
return writer;
}
+
+ @Override
+ public void writeToClass(AndroidResourceClassWriter writer) {
+ }
}
/** Represents an Android Fraction Attribute resource. */
@@ -694,5 +752,9 @@ public class AttrXmlResourceValue implements XmlResourceValue {
public ValuesResourceDefinition writeTo(ValuesResourceDefinition writer) {
return writer;
}
+
+ @Override
+ public void writeToClass(AndroidResourceClassWriter writer) {
+ }
}
}
diff --git a/src/tools/android/java/com/google/devtools/build/android/xml/IdXmlResourceValue.java b/src/tools/android/java/com/google/devtools/build/android/xml/IdXmlResourceValue.java
index 1b56f8b0c7..d9a36488e6 100644
--- a/src/tools/android/java/com/google/devtools/build/android/xml/IdXmlResourceValue.java
+++ b/src/tools/android/java/com/google/devtools/build/android/xml/IdXmlResourceValue.java
@@ -16,6 +16,7 @@ package com.google.devtools.build.android.xml;
import com.google.common.base.MoreObjects;
import com.google.devtools.build.android.AndroidDataWritingVisitor;
import com.google.devtools.build.android.AndroidDataWritingVisitor.StartTag;
+import com.google.devtools.build.android.AndroidResourceClassWriter;
import com.google.devtools.build.android.FullyQualifiedName;
import com.google.devtools.build.android.XmlResourceValue;
import com.google.devtools.build.android.XmlResourceValues;
@@ -80,6 +81,12 @@ public class IdXmlResourceValue implements XmlResourceValue {
}
@Override
+ public void writeResourceToClass(FullyQualifiedName key,
+ AndroidResourceClassWriter resourceClassWriter) {
+ resourceClassWriter.writeSimpleResource(key.type(), key.name());
+ }
+
+ @Override
public int serializeTo(Path source, Namespaces namespaces, OutputStream output)
throws IOException {
Builder xmlValue =
diff --git a/src/tools/android/java/com/google/devtools/build/android/xml/PluralXmlResourceValue.java b/src/tools/android/java/com/google/devtools/build/android/xml/PluralXmlResourceValue.java
index 1e708afb1c..a8e92e4b19 100644
--- a/src/tools/android/java/com/google/devtools/build/android/xml/PluralXmlResourceValue.java
+++ b/src/tools/android/java/com/google/devtools/build/android/xml/PluralXmlResourceValue.java
@@ -17,6 +17,7 @@ import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableMap;
import com.google.devtools.build.android.AndroidDataWritingVisitor;
import com.google.devtools.build.android.AndroidDataWritingVisitor.ValuesResourceDefinition;
+import com.google.devtools.build.android.AndroidResourceClassWriter;
import com.google.devtools.build.android.FullyQualifiedName;
import com.google.devtools.build.android.XmlResourceValue;
import com.google.devtools.build.android.XmlResourceValues;
@@ -97,6 +98,12 @@ public class PluralXmlResourceValue implements XmlResourceValue {
}
@Override
+ public void writeResourceToClass(FullyQualifiedName key,
+ AndroidResourceClassWriter resourceClassWriter) {
+ resourceClassWriter.writeSimpleResource(key.type(), key.name());
+ }
+
+ @Override
public int hashCode() {
return Objects.hash(attributes, values);
}
diff --git a/src/tools/android/java/com/google/devtools/build/android/xml/SimpleXmlResourceValue.java b/src/tools/android/java/com/google/devtools/build/android/xml/SimpleXmlResourceValue.java
index ef4e822b4f..bc7c44a3f0 100644
--- a/src/tools/android/java/com/google/devtools/build/android/xml/SimpleXmlResourceValue.java
+++ b/src/tools/android/java/com/google/devtools/build/android/xml/SimpleXmlResourceValue.java
@@ -18,6 +18,7 @@ import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableMap;
import com.google.devtools.build.android.AndroidDataWritingVisitor;
import com.google.devtools.build.android.AndroidDataWritingVisitor.StartTag;
+import com.google.devtools.build.android.AndroidResourceClassWriter;
import com.google.devtools.build.android.FullyQualifiedName;
import com.google.devtools.build.android.XmlResourceValue;
import com.google.devtools.build.android.XmlResourceValues;
@@ -211,6 +212,12 @@ public class SimpleXmlResourceValue implements XmlResourceValue {
}
@Override
+ public void writeResourceToClass(FullyQualifiedName key,
+ AndroidResourceClassWriter resourceClassWriter) {
+ resourceClassWriter.writeSimpleResource(key.type(), key.name());
+ }
+
+ @Override
public int serializeTo(Path source, Namespaces namespaces, OutputStream output)
throws IOException {
SerializeFormat.DataValue.Builder builder =
diff --git a/src/tools/android/java/com/google/devtools/build/android/xml/StyleXmlResourceValue.java b/src/tools/android/java/com/google/devtools/build/android/xml/StyleXmlResourceValue.java
index 60abda34fe..1bd20dab77 100644
--- a/src/tools/android/java/com/google/devtools/build/android/xml/StyleXmlResourceValue.java
+++ b/src/tools/android/java/com/google/devtools/build/android/xml/StyleXmlResourceValue.java
@@ -18,6 +18,7 @@ import com.google.common.base.MoreObjects;
import com.google.common.collect.ImmutableMap;
import com.google.devtools.build.android.AndroidDataWritingVisitor;
import com.google.devtools.build.android.AndroidDataWritingVisitor.ValuesResourceDefinition;
+import com.google.devtools.build.android.AndroidResourceClassWriter;
import com.google.devtools.build.android.FullyQualifiedName;
import com.google.devtools.build.android.XmlResourceValue;
import com.google.devtools.build.android.XmlResourceValues;
@@ -105,6 +106,12 @@ public class StyleXmlResourceValue implements XmlResourceValue {
}
@Override
+ public void writeResourceToClass(FullyQualifiedName key,
+ AndroidResourceClassWriter resourceClassWriter) {
+ resourceClassWriter.writeSimpleResource(key.type(), key.name());
+ }
+
+ @Override
public int serializeTo(Path source, Namespaces namespaces, OutputStream output)
throws IOException {
SerializeFormat.DataValueXml.Builder xmlValueBuilder =
diff --git a/src/tools/android/java/com/google/devtools/build/android/xml/StyleableXmlResourceValue.java b/src/tools/android/java/com/google/devtools/build/android/xml/StyleableXmlResourceValue.java
index 82bf56584a..d37e34d5b0 100644
--- a/src/tools/android/java/com/google/devtools/build/android/xml/StyleableXmlResourceValue.java
+++ b/src/tools/android/java/com/google/devtools/build/android/xml/StyleableXmlResourceValue.java
@@ -21,6 +21,7 @@ import com.google.common.collect.ImmutableMap.Builder;
import com.google.common.collect.Iterables;
import com.google.devtools.build.android.AndroidDataWritingVisitor;
import com.google.devtools.build.android.AndroidDataWritingVisitor.ValuesResourceDefinition;
+import com.google.devtools.build.android.AndroidResourceClassWriter;
import com.google.devtools.build.android.FullyQualifiedName;
import com.google.devtools.build.android.XmlResourceValue;
import com.google.devtools.build.android.XmlResourceValues;
@@ -136,6 +137,12 @@ public class StyleableXmlResourceValue implements XmlResourceValue {
}
@Override
+ public void writeResourceToClass(FullyQualifiedName key,
+ AndroidResourceClassWriter resourceClassWriter) {
+ resourceClassWriter.writeStyleableResource(key, attrs);
+ }
+
+ @Override
public int serializeTo(Path source, Namespaces namespaces, OutputStream output)
throws IOException {
return XmlResourceValues.serializeProtoDataValue(