diff options
Diffstat (limited to 'src/tools/android/java')
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( |