aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/tools/android/java/com
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools/android/java/com')
-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(