| // Copyright 2017 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 com.google.common.truth.Truth.assertAbout; | |
| import static com.google.common.truth.Truth.assertThat; | |
| import com.android.resources.ResourceType; | |
| import com.google.common.base.Function; | |
| import com.google.common.base.Joiner; | |
| import com.google.common.base.Optional; | |
| import com.google.common.collect.FluentIterable; | |
| import com.google.common.collect.ImmutableList; | |
| import com.google.common.collect.ImmutableMap; | |
| import com.google.common.jimfs.Jimfs; | |
| import com.google.common.truth.Subject; | |
| import com.google.devtools.build.android.AndroidResourceMerger.MergingException; | |
| import com.google.devtools.build.android.xml.ArrayXmlResourceValue; | |
| import com.google.devtools.build.android.xml.ArrayXmlResourceValue.ArrayType; | |
| import com.google.devtools.build.android.xml.AttrXmlResourceValue; | |
| import com.google.devtools.build.android.xml.AttrXmlResourceValue.BooleanResourceXmlAttrValue; | |
| import com.google.devtools.build.android.xml.AttrXmlResourceValue.ColorResourceXmlAttrValue; | |
| import com.google.devtools.build.android.xml.AttrXmlResourceValue.DimensionResourceXmlAttrValue; | |
| import com.google.devtools.build.android.xml.AttrXmlResourceValue.EnumResourceXmlAttrValue; | |
| import com.google.devtools.build.android.xml.AttrXmlResourceValue.FlagResourceXmlAttrValue; | |
| import com.google.devtools.build.android.xml.AttrXmlResourceValue.FloatResourceXmlAttrValue; | |
| import com.google.devtools.build.android.xml.AttrXmlResourceValue.FractionResourceXmlAttrValue; | |
| import com.google.devtools.build.android.xml.AttrXmlResourceValue.IntegerResourceXmlAttrValue; | |
| import com.google.devtools.build.android.xml.AttrXmlResourceValue.ReferenceResourceXmlAttrValue; | |
| import com.google.devtools.build.android.xml.AttrXmlResourceValue.StringResourceXmlAttrValue; | |
| import com.google.devtools.build.android.xml.IdXmlResourceValue; | |
| import com.google.devtools.build.android.xml.Namespaces; | |
| import com.google.devtools.build.android.xml.PluralXmlResourceValue; | |
| import com.google.devtools.build.android.xml.PublicXmlResourceValue; | |
| import com.google.devtools.build.android.xml.ResourcesAttribute; | |
| import com.google.devtools.build.android.xml.SimpleXmlResourceValue; | |
| import com.google.devtools.build.android.xml.StyleXmlResourceValue; | |
| import com.google.devtools.build.android.xml.StyleableXmlResourceValue; | |
| import java.io.IOException; | |
| import java.nio.charset.StandardCharsets; | |
| import java.nio.file.FileSystem; | |
| import java.nio.file.FileVisitResult; | |
| import java.nio.file.Files; | |
| import java.nio.file.Path; | |
| import java.nio.file.SimpleFileVisitor; | |
| import java.nio.file.attribute.BasicFileAttributes; | |
| import java.util.HashMap; | |
| import java.util.Map; | |
| import javax.xml.stream.XMLStreamException; | |
| import org.junit.Before; | |
| import org.junit.Test; | |
| import org.junit.runner.RunWith; | |
| import org.junit.runners.JUnit4; | |
| /** Tests for {@link DataResourceXml}. */ | |
| @RunWith(JUnit4.class) | |
| public class DataResourceXmlTest { | |
| static final ImmutableMap<String, String> XLIFF_NAMESPACES = | |
| ImmutableMap.of("xliff", "urn:oasis:names:tc:xliff:document:1.2"); | |
| static final String END_RESOURCES = new String(AndroidDataWriter.END_RESOURCES); | |
| private FullyQualifiedName.Factory fqnFactory; | |
| private FileSystem fs; | |
| @Before | |
| public void createCleanEnvironment() { | |
| fs = Jimfs.newFileSystem(); | |
| fqnFactory = FullyQualifiedName.Factory.from(ImmutableList.<String>of()); | |
| } | |
| private Path writeResourceXml(String... xml) throws IOException { | |
| return writeResourceXml(ImmutableMap.<String, String>of(), ImmutableMap.<String, String>of(), | |
| xml); | |
| } | |
| private Path writeResourceXml(Map<String, String> namespaces, Map<String, String> attributes, | |
| String... xml) throws IOException { | |
| Path values = fs.getPath("root/values/values.xml"); | |
| Files.createDirectories(values.getParent()); | |
| StringBuilder builder = new StringBuilder(); | |
| builder.append(AndroidDataWriter.PRELUDE).append("<resources"); | |
| for (Map.Entry<String, String> entry : namespaces.entrySet()) { | |
| builder | |
| .append(" xmlns:") | |
| .append(entry.getKey()) | |
| .append("=\"") | |
| .append(entry.getValue()) | |
| .append("\""); | |
| } | |
| for (Map.Entry<String, String> entry : attributes.entrySet()) { | |
| builder | |
| .append(" ") | |
| .append(entry.getKey()) | |
| .append("=\"") | |
| .append(entry.getValue()) | |
| .append("\""); | |
| } | |
| builder.append(">"); | |
| Joiner.on("\n").appendTo(builder, xml); | |
| builder.append(END_RESOURCES); | |
| Files.write(values, builder.toString().getBytes(StandardCharsets.UTF_8)); | |
| return values; | |
| } | |
| private void parseResourcesFrom( | |
| Path path, Map<DataKey, DataResource> toOverwrite, Map<DataKey, DataResource> toCombine) | |
| throws XMLStreamException, IOException { | |
| DataResourceXml.parse( | |
| XmlResourceValues.getXmlInputFactory(), | |
| path, | |
| fqnFactory, | |
| new FakeConsumer(toOverwrite), | |
| new FakeConsumer(toCombine)); | |
| } | |
| private FullyQualifiedName fqn(String raw) { | |
| return fqnFactory.parse(raw); | |
| } | |
| @Test | |
| public void simpleXmlResources() throws Exception { | |
| Path path = | |
| writeResourceXml( | |
| "<string name='exit' description=\"& egress -> "\">way out</string>", | |
| "<bool name='canExit'>false</bool>", | |
| "<color name='exitColor'>#FF000000</color>", | |
| "<dimen name='exitSize'>20sp</dimen>", | |
| "<integer name='exitInt'>20</integer>", | |
| "<fraction name='exitFraction'>%20</fraction>", | |
| "<drawable name='exitBackground'>#99000000</drawable>", | |
| "<item name='some_id' type='id'/>", | |
| "<item name='reference_id' type='id'>@id/some_id</item>"); | |
| final Map<DataKey, DataResource> toOverwrite = new HashMap<>(); | |
| final Map<DataKey, DataResource> toCombine = new HashMap<>(); | |
| parseResourcesFrom(path, toOverwrite, toCombine); | |
| assertThat(toOverwrite) | |
| .containsExactly( | |
| fqn("string/exit"), // Key | |
| DataResourceXml.createWithNoNamespace( | |
| path, | |
| SimpleXmlResourceValue.of( | |
| SimpleXmlResourceValue.Type.STRING, | |
| ImmutableMap.of("description", "& egress -> ""), | |
| "way out")), // Value | |
| fqn("bool/canExit"), // Key | |
| DataResourceXml.createWithNoNamespace( | |
| path, | |
| SimpleXmlResourceValue.createWithValue( | |
| SimpleXmlResourceValue.Type.BOOL, "false")), // Value | |
| fqn("color/exitColor"), // Key | |
| DataResourceXml.createWithNoNamespace( | |
| path, | |
| SimpleXmlResourceValue.createWithValue( | |
| SimpleXmlResourceValue.Type.COLOR, "#FF000000")), // Value | |
| fqn("dimen/exitSize"), // Key | |
| DataResourceXml.createWithNoNamespace( | |
| path, | |
| SimpleXmlResourceValue.createWithValue( | |
| SimpleXmlResourceValue.Type.DIMEN, "20sp")), // Value | |
| fqn("integer/exitInt"), // Key | |
| DataResourceXml.createWithNoNamespace( | |
| path, | |
| SimpleXmlResourceValue.createWithValue( | |
| SimpleXmlResourceValue.Type.INTEGER, "20")), // Value | |
| fqn("fraction/exitFraction"), // Key | |
| DataResourceXml.createWithNoNamespace( | |
| path, | |
| SimpleXmlResourceValue.createWithValue( | |
| SimpleXmlResourceValue.Type.FRACTION, "%20")), // Value | |
| fqn("drawable/exitBackground"), // Key | |
| DataResourceXml.createWithNoNamespace( | |
| path, | |
| SimpleXmlResourceValue.createWithValue( | |
| SimpleXmlResourceValue.Type.DRAWABLE, "#99000000")) // Value | |
| ); | |
| assertThat(toCombine) | |
| .containsExactly( | |
| fqn("id/some_id"), // Key | |
| DataResourceXml.createWithNoNamespace(path, IdXmlResourceValue.of()), // Value | |
| fqn("id/reference_id"), // Key | |
| DataResourceXml.createWithNoNamespace( | |
| path, IdXmlResourceValue.of("@id/some_id")) // Value | |
| ); | |
| } | |
| @Test | |
| public void skipIgnored() throws Exception { | |
| Path path = writeResourceXml("<skip/>"); | |
| final Map<DataKey, DataResource> toOverwrite = new HashMap<>(); | |
| final Map<DataKey, DataResource> toCombine = new HashMap<>(); | |
| parseResourcesFrom(path, toOverwrite, toCombine); | |
| assertThat(toOverwrite).isEmpty(); | |
| assertThat(toCombine).isEmpty(); | |
| } | |
| @Test | |
| public void eatCommentIgnored() throws Exception { | |
| Path path = writeResourceXml("<eat-comment/>"); | |
| final Map<DataKey, DataResource> toOverwrite = new HashMap<>(); | |
| final Map<DataKey, DataResource> toCombine = new HashMap<>(); | |
| parseResourcesFrom(path, toOverwrite, toCombine); | |
| assertThat(toOverwrite).isEmpty(); | |
| assertThat(toCombine).isEmpty(); | |
| } | |
| @Test | |
| public void itemSimpleXmlResources() throws Exception { | |
| Path path = | |
| writeResourceXml( | |
| "<item type='dimen' name='exitSizePercent'>20%</item>", | |
| "<item type='dimen' format='float' name='exitSizeFloat'>20.0</item>", | |
| "<item type='fraction' name='denom'>%5</item>", | |
| "<item name='subtype_id' type='id'>0x6f972360</item>", | |
| "<item type='array' name='oboes'/>", | |
| "<item type='drawable' name='placeholder'/>"); | |
| final Map<DataKey, DataResource> toOverwrite = new HashMap<>(); | |
| final Map<DataKey, DataResource> toCombine = new HashMap<>(); | |
| parseResourcesFrom(path, toOverwrite, toCombine); | |
| assertThat(toOverwrite) | |
| .containsExactly( | |
| fqn("dimen/exitSizePercent"), // Key | |
| DataResourceXml.createWithNoNamespace( | |
| path, SimpleXmlResourceValue.itemWithValue(ResourceType.DIMEN, "20%")), // Value | |
| fqn("dimen/exitSizeFloat"), // Key | |
| DataResourceXml.createWithNoNamespace( | |
| path, | |
| SimpleXmlResourceValue.itemWithFormattedValue( | |
| ResourceType.DIMEN, "float", "20.0")), // Value | |
| fqn("drawable/placeholder"), // Key | |
| DataResourceXml.createWithNoNamespace( | |
| path, SimpleXmlResourceValue.itemPlaceHolderFor(ResourceType.DRAWABLE)), // Value | |
| fqn("array/oboes"), // Key | |
| DataResourceXml.createWithNoNamespace( | |
| path, SimpleXmlResourceValue.itemPlaceHolderFor(ResourceType.ARRAY)), // Value | |
| fqn("fraction/denom"), // Key | |
| DataResourceXml.createWithNoNamespace( | |
| path, SimpleXmlResourceValue.itemWithValue(ResourceType.FRACTION, "%5")) // Value | |
| ); | |
| } | |
| @Test | |
| public void styleableXmlResourcesEnum() throws Exception { | |
| Path path = | |
| writeResourceXml( | |
| "<declare-styleable name='Theme'>", | |
| " <attr name=\"labelPosition\" format=\"enum\">", | |
| " <enum name=\"left\" value=\"0\"/>", | |
| " <enum name=\"right\" value=\"1\"/>", | |
| " </attr>", | |
| "</declare-styleable>"); | |
| final Map<DataKey, DataResource> toOverwrite = new HashMap<>(); | |
| final Map<DataKey, DataResource> toCombine = new HashMap<>(); | |
| parseResourcesFrom(path, toOverwrite, toCombine); | |
| assertThat(toOverwrite) | |
| .containsExactly( | |
| fqn("attr/labelPosition"), // Key | |
| DataResourceXml.createWithNoNamespace( | |
| path, | |
| AttrXmlResourceValue.fromFormatEntries( | |
| EnumResourceXmlAttrValue.asEntryOf("left", "0", "right", "1"))) // Value | |
| ); | |
| assertThat(toCombine) | |
| .containsExactly( | |
| fqn("styleable/Theme"), // Key | |
| DataResourceXml.createWithNoNamespace( | |
| path, | |
| StyleableXmlResourceValue.createAllAttrAsDefinitions( | |
| fqnFactory.parse("attr/labelPosition"))) // Value | |
| ); | |
| } | |
| @Test | |
| public void styleableXmlResourcesString() throws Exception { | |
| Path path = | |
| writeResourceXml( | |
| "<declare-styleable name='UnusedStyleable'>", | |
| " <attr name='attribute1'/>", | |
| " <attr name='attribute2'/>", | |
| "</declare-styleable>", | |
| "<attr name='attribute1' format='string'/>", | |
| "<attr name='attribute2' format='string'/>"); | |
| final Map<DataKey, DataResource> toOverwrite = new HashMap<>(); | |
| final Map<DataKey, DataResource> toCombine = new HashMap<>(); | |
| parseResourcesFrom(path, toOverwrite, toCombine); | |
| assertThat(toOverwrite) | |
| .containsExactly( | |
| fqn("attr/attribute1"), // Key | |
| DataResourceXml.createWithNoNamespace( | |
| path, | |
| AttrXmlResourceValue.fromFormatEntries( | |
| StringResourceXmlAttrValue.asEntry())), // Value | |
| fqn("attr/attribute2"), // Key | |
| DataResourceXml.createWithNoNamespace( | |
| path, | |
| AttrXmlResourceValue.fromFormatEntries( | |
| StringResourceXmlAttrValue.asEntry())) // Value | |
| ); | |
| assertThat(toCombine) | |
| .containsExactly( | |
| fqn("styleable/UnusedStyleable"), // Key | |
| DataResourceXml.createWithNoNamespace( | |
| path, | |
| StyleableXmlResourceValue.createAllAttrAsReferences( | |
| fqnFactory.parse("attr/attribute1"), | |
| fqnFactory.parse("attr/attribute2"))) // Value | |
| ); | |
| } | |
| @Test | |
| public void pluralXmlResources() throws Exception { | |
| Path path = | |
| writeResourceXml( | |
| "<plurals name='numberOfSongsAvailable'>", | |
| " <item quantity='one'>%d song found.</item>", | |
| " <item quantity='other'>%d songs found.</item>", | |
| "</plurals>"); | |
| final Map<DataKey, DataResource> toOverwrite = new HashMap<>(); | |
| final Map<DataKey, DataResource> toCombine = new HashMap<>(); | |
| parseResourcesFrom(path, toOverwrite, toCombine); | |
| assertThat(toOverwrite) | |
| .containsExactly( | |
| fqn("plurals/numberOfSongsAvailable"), // Key | |
| DataResourceXml.createWithNoNamespace( | |
| path, | |
| PluralXmlResourceValue.createWithoutAttributes( | |
| ImmutableMap.of( | |
| "one", "%d song found.", | |
| "other", "%d songs found."))) // Value | |
| ); | |
| } | |
| @Test | |
| public void pluralXmlResourcesWithTrailingCharacters() throws Exception { | |
| Path path = | |
| writeResourceXml( | |
| "<plurals name='numberOfSongsAvailable'>", | |
| " <item quantity='one'>%d song found.</item> // this is an invalid comment.", | |
| " <item quantity='other'>%d songs found.</item>typo", | |
| "</plurals>"); | |
| final Map<DataKey, DataResource> toOverwrite = new HashMap<>(); | |
| final Map<DataKey, DataResource> toCombine = new HashMap<>(); | |
| parseResourcesFrom(path, toOverwrite, toCombine); | |
| assertThat(toOverwrite) | |
| .containsExactly( | |
| fqn("plurals/numberOfSongsAvailable"), // Key | |
| DataResourceXml.createWithNoNamespace( | |
| path, | |
| PluralXmlResourceValue.createWithoutAttributes( | |
| ImmutableMap.of( | |
| "one", "%d song found.", | |
| "other", "%d songs found."))) // Value | |
| ); | |
| } | |
| @Test | |
| public void styleResources() throws Exception { | |
| Path path = | |
| writeResourceXml( | |
| "<style name=\"candlestick_maker\" parent=\"@style/wax_maker\">\n" | |
| + " <item name=\"texture\">waxy</item>\n" | |
| + "</style>"); | |
| final Map<DataKey, DataResource> toOverwrite = new HashMap<>(); | |
| final Map<DataKey, DataResource> toCombine = new HashMap<>(); | |
| parseResourcesFrom(path, toOverwrite, toCombine); | |
| assertThat(toOverwrite) | |
| .containsExactly( | |
| fqn("style/candlestick_maker"), // Key | |
| DataResourceXml.createWithNoNamespace( | |
| path, | |
| StyleXmlResourceValue.of( | |
| "@style/wax_maker", ImmutableMap.of("texture", "waxy"))) // Value | |
| ); | |
| } | |
| @Test | |
| public void styleResourcesNoParent() throws Exception { | |
| Path path = | |
| writeResourceXml( | |
| "<style name=\"CandlestickMaker\">\n" | |
| + " <item name=\"texture\">waxy</item>\n" | |
| + "</style>"); | |
| final Map<DataKey, DataResource> toOverwrite = new HashMap<>(); | |
| final Map<DataKey, DataResource> toCombine = new HashMap<>(); | |
| parseResourcesFrom(path, toOverwrite, toCombine); | |
| assertThat(toOverwrite) | |
| .containsExactly( | |
| fqn("style/CandlestickMaker"), // Key | |
| DataResourceXml.createWithNoNamespace( | |
| path, StyleXmlResourceValue.of(null, ImmutableMap.of("texture", "waxy"))) // Value | |
| ); | |
| } | |
| @Test | |
| public void styleResourcesForceNoParent() throws Exception { | |
| Path path = | |
| writeResourceXml( | |
| // using a '.' implies a parent Candlestick, parent='' corrects to no parent. | |
| "<style name=\"Candlestick.Maker\" parent=\"\">\n" | |
| + " <item name=\"texture\">waxy</item>\n" | |
| + "</style>"); | |
| final Map<DataKey, DataResource> toOverwrite = new HashMap<>(); | |
| final Map<DataKey, DataResource> toCombine = new HashMap<>(); | |
| parseResourcesFrom(path, toOverwrite, toCombine); | |
| assertThat(toOverwrite) | |
| .containsExactly( | |
| fqn("style/Candlestick.Maker"), // Key | |
| DataResourceXml.createWithNoNamespace( | |
| path, StyleXmlResourceValue.of("", ImmutableMap.of("texture", "waxy"))) // Value | |
| ); | |
| } | |
| @Test | |
| public void styleResourcesLazyReference() throws Exception { | |
| Path path = | |
| writeResourceXml( | |
| "<style name=\"candlestick_maker\" parent=\"AppTheme\">\n" | |
| + " <item name=\"texture\">waxy</item>\n" | |
| + "</style>"); | |
| final Map<DataKey, DataResource> toOverwrite = new HashMap<>(); | |
| final Map<DataKey, DataResource> toCombine = new HashMap<>(); | |
| parseResourcesFrom(path, toOverwrite, toCombine); | |
| assertThat(toOverwrite) | |
| .containsExactly( | |
| fqn("style/candlestick_maker"), // Key | |
| DataResourceXml.createWithNoNamespace( | |
| path, | |
| StyleXmlResourceValue.of("AppTheme", ImmutableMap.of("texture", "waxy"))) // Value | |
| ); | |
| } | |
| @Test | |
| public void arrayXmlResources() throws Exception { | |
| Path path = | |
| writeResourceXml( | |
| "<array name='icons'>", | |
| " <item>@drawable/home</item>", | |
| " <item>@drawable/settings</item>", | |
| " <item>@drawable/logout</item>", | |
| "</array>", | |
| "<array name='colors'>", | |
| " <item>#FFFF0000</item>", | |
| " <item>#FF00FF00</item>", | |
| " <item>#FF0000FF</item>", | |
| "</array>"); | |
| final Map<DataKey, DataResource> toOverwrite = new HashMap<>(); | |
| final Map<DataKey, DataResource> toCombine = new HashMap<>(); | |
| parseResourcesFrom(path, toOverwrite, toCombine); | |
| assertThat(toOverwrite) | |
| .containsExactly( | |
| fqn("array/icons"), // Key | |
| DataResourceXml.createWithNoNamespace( | |
| path, | |
| ArrayXmlResourceValue.of( | |
| ArrayType.ARRAY, | |
| "@drawable/home", | |
| "@drawable/settings", | |
| "@drawable/logout")), // Value | |
| fqn("array/colors"), // Key | |
| DataResourceXml.createWithNoNamespace( | |
| path, | |
| ArrayXmlResourceValue.of( | |
| ArrayType.ARRAY, "#FFFF0000", "#FF00FF00", "#FF0000FF")) // Value | |
| ); | |
| } | |
| @Test | |
| public void stringArrayXmlResources() throws Exception { | |
| Path path = | |
| writeResourceXml( | |
| "<string-array name='characters'>", | |
| " <item>boojum</item>", | |
| " <item>snark</item>", | |
| " <item>bellman</item>", | |
| " <item>barrister</item>", | |
| " <item>\\\"billiard-marker\\\"</item>", | |
| "</string-array>"); | |
| final Map<DataKey, DataResource> toOverwrite = new HashMap<>(); | |
| final Map<DataKey, DataResource> toCombine = new HashMap<>(); | |
| parseResourcesFrom(path, toOverwrite, toCombine); | |
| assertThat(toOverwrite) | |
| .containsExactly( | |
| fqn("array/characters"), | |
| DataResourceXml.createWithNoNamespace( | |
| path, | |
| ArrayXmlResourceValue.of( | |
| ArrayType.STRING_ARRAY, | |
| "boojum", | |
| "snark", | |
| "bellman", | |
| "barrister", | |
| "\\\"billiard-marker\\\""))); | |
| assertThat(toCombine).isEmpty(); | |
| } | |
| @Test | |
| public void integerArrayXmlResources() throws Exception { | |
| Path path = | |
| writeResourceXml( | |
| "<integer-array name='bits'>", | |
| " <item>4</item>", | |
| " <item>8</item>", | |
| " <item>16</item>", | |
| " <item>32</item>", | |
| "</integer-array>"); | |
| final Map<DataKey, DataResource> toOverwrite = new HashMap<>(); | |
| final Map<DataKey, DataResource> toCombine = new HashMap<>(); | |
| parseResourcesFrom(path, toOverwrite, toCombine); | |
| assertThat(toOverwrite) | |
| .containsExactly( | |
| fqn("array/bits"), // Key | |
| DataResourceXml.createWithNoNamespace( | |
| path, | |
| ArrayXmlResourceValue.of(ArrayType.INTEGER_ARRAY, "4", "8", "16", "32")) // Value | |
| ); | |
| } | |
| @Test | |
| public void attrFlagXmlResources() throws Exception { | |
| Path path = | |
| writeResourceXml( | |
| " <attr name=\"labelPosition\">", | |
| " <flag name=\"left\" value=\"0\"/>", | |
| " <flag name=\"right\" value=\"1\"/>", | |
| " </attr>"); | |
| final Map<DataKey, DataResource> toOverwrite = new HashMap<>(); | |
| final Map<DataKey, DataResource> toCombine = new HashMap<>(); | |
| parseResourcesFrom(path, toOverwrite, toCombine); | |
| assertThat(toOverwrite) | |
| .containsExactly( | |
| fqn("attr/labelPosition"), // Key | |
| DataResourceXml.createWithNoNamespace( | |
| path, | |
| AttrXmlResourceValue.fromFormatEntries( | |
| FlagResourceXmlAttrValue.asEntryOf( | |
| "left", "0", | |
| "right", "1"))) // Value | |
| ); | |
| } | |
| @Test | |
| public void attrMultiFormatImplicitFlagXmlResources() throws Exception { | |
| Path path = | |
| writeResourceXml( | |
| " <attr name='labelPosition' format='reference'>", | |
| " <flag name='left' value='0'/>", | |
| " <flag name='right' value='1'/>", | |
| " </attr>"); | |
| final Map<DataKey, DataResource> toOverwrite = new HashMap<>(); | |
| final Map<DataKey, DataResource> toCombine = new HashMap<>(); | |
| parseResourcesFrom(path, toOverwrite, toCombine); | |
| assertThat(toOverwrite) | |
| .containsExactly( | |
| fqn("attr/labelPosition"), // Key | |
| DataResourceXml.createWithNoNamespace( | |
| path, | |
| AttrXmlResourceValue.fromFormatEntries( | |
| ReferenceResourceXmlAttrValue.asEntry(), | |
| FlagResourceXmlAttrValue.asEntryOf( | |
| "left", "0", | |
| "right", "1"))) // Value | |
| ); | |
| } | |
| @Test | |
| public void attrMultiFormatResources() throws Exception { | |
| Path path = | |
| writeResourceXml( | |
| "<attr name='labelPosition' ", | |
| "format='color|boolean|dimension|float|integer|string|fraction|reference' />"); | |
| final Map<DataKey, DataResource> toOverwrite = new HashMap<>(); | |
| final Map<DataKey, DataResource> toCombine = new HashMap<>(); | |
| parseResourcesFrom(path, toOverwrite, toCombine); | |
| assertThat(toOverwrite) | |
| .containsExactly( | |
| fqn("attr/labelPosition"), // Key | |
| DataResourceXml.createWithNoNamespace( | |
| path, | |
| AttrXmlResourceValue.fromFormatEntries( | |
| ColorResourceXmlAttrValue.asEntry(), | |
| BooleanResourceXmlAttrValue.asEntry(), | |
| ReferenceResourceXmlAttrValue.asEntry(), | |
| DimensionResourceXmlAttrValue.asEntry(), | |
| FloatResourceXmlAttrValue.asEntry(), | |
| IntegerResourceXmlAttrValue.asEntry(), | |
| StringResourceXmlAttrValue.asEntry(), | |
| FractionResourceXmlAttrValue.asEntry())) // Value | |
| ); | |
| } | |
| @Test | |
| public void publicXmlResource() throws Exception { | |
| Path path = | |
| writeResourceXml( | |
| "<string name='exit'>way out</string>", | |
| "<public type='string' name='exit' id='0x123'/>"); | |
| final Map<DataKey, DataResource> toOverwrite = new HashMap<>(); | |
| final Map<DataKey, DataResource> toCombine = new HashMap<>(); | |
| parseResourcesFrom(path, toOverwrite, toCombine); | |
| assertThat(toOverwrite) | |
| .containsExactly( | |
| fqn("string/exit"), | |
| DataResourceXml.createWithNoNamespace( | |
| path, | |
| SimpleXmlResourceValue.createWithValue( | |
| SimpleXmlResourceValue.Type.STRING, "way out"))); | |
| assertThat(toCombine) | |
| .containsExactly( | |
| fqn("public/exit"), | |
| DataResourceXml.createWithNoNamespace( | |
| path, PublicXmlResourceValue.create(ResourceType.STRING, Optional.of(0x123)))); | |
| } | |
| @Test | |
| public void resourcesAttribute() throws Exception { | |
| Namespaces namespaces = Namespaces.from( | |
| ImmutableMap.of("tools", "http://schemas.android.com/tools")); | |
| Path path = writeResourceXml( | |
| namespaces.asMap(), | |
| ImmutableMap.of("tools:foo", "fooVal", "tools:ignore", "ignoreVal")); | |
| final Map<DataKey, DataResource> toOverwrite = new HashMap<>(); | |
| final Map<DataKey, DataResource> toCombine = new HashMap<>(); | |
| parseResourcesFrom(path, toOverwrite, toCombine); | |
| FullyQualifiedName fooFqn = fqn("<resources>/{http://schemas.android.com/tools}foo"); | |
| assertThat(toOverwrite) | |
| .containsExactly( | |
| fooFqn, | |
| DataResourceXml.createWithNamespaces( | |
| path, | |
| ResourcesAttribute.of(fooFqn, "tools:foo", "fooVal"), | |
| namespaces) | |
| ); | |
| FullyQualifiedName ignoreFqn = fqn("<resources>/{http://schemas.android.com/tools}ignore"); | |
| assertThat(toCombine) | |
| .containsExactly( | |
| ignoreFqn, | |
| DataResourceXml.createWithNamespaces( | |
| path, | |
| ResourcesAttribute.of(ignoreFqn, "tools:ignore", "ignoreVal"), | |
| namespaces) | |
| ); | |
| } | |
| @Test | |
| public void writeSimpleXmlResources() throws Exception { | |
| Path source = | |
| writeResourceXml( | |
| "<string name='exit'>way <a href=\"out.html\">out</a></string>", | |
| "<bool name='canExit'>false</bool>", | |
| "<color name='exitColor'>#FF000000</color>", | |
| "<integer name='exitInt'>5</integer>", | |
| "<drawable name='reference'/>"); | |
| assertAbout(resourcePaths) | |
| .that(parsedAndWritten(source, fqn("string/exit"))) | |
| .xmlContentsIsEqualTo( | |
| resourcesXmlFrom( | |
| source, "<string name='exit'>way <a href=\"out.html\">out</a></string>")); | |
| assertAbout(resourcePaths) | |
| .that(parsedAndWritten(source, fqn("bool/canExit"))) | |
| .xmlContentsIsEqualTo(resourcesXmlFrom(source, "<bool name='canExit'>false</bool>")); | |
| assertAbout(resourcePaths) | |
| .that(parsedAndWritten(source, fqn("color/exitColor"))) | |
| .xmlContentsIsEqualTo( | |
| resourcesXmlFrom(source, "<color name='exitColor'>#FF000000</color>")); | |
| assertAbout(resourcePaths) | |
| .that(parsedAndWritten(source, fqn("integer/exitInt"))) | |
| .xmlContentsIsEqualTo(resourcesXmlFrom(source, "<integer name='exitInt'>5</integer>")); | |
| } | |
| @Test | |
| public void writeItemResources() throws Exception { | |
| Path source = | |
| writeResourceXml( | |
| "<item type='dimen' name='exitSizePercent'>20%</item>", | |
| "<item type='dimen' format='float' name='exitSizeFloat'>20.0</item>", | |
| "<item name='exitId' type='id'/>", | |
| "<item name='frac' type='fraction'>5%</item>"); | |
| assertAbout(resourcePaths) | |
| .that(parsedAndWritten(source, fqn("dimen/exitSizePercent"))) | |
| .xmlContentsIsEqualTo( | |
| resourcesXmlFrom(source, "<item type='dimen' name='exitSizePercent'>20%</item>")); | |
| assertAbout(resourcePaths) | |
| .that(parsedAndWritten(source, fqn("dimen/exitSizeFloat"))) | |
| .xmlContentsIsEqualTo( | |
| resourcesXmlFrom( | |
| source, "<item type='dimen' format='float' name='exitSizeFloat'>20.0</item>")); | |
| assertAbout(resourcePaths) | |
| .that(parsedAndWritten(source, fqn("fraction/frac"))) | |
| .xmlContentsIsEqualTo( | |
| resourcesXmlFrom(source, "<item name='frac' type='fraction'>5%</item>")); | |
| assertAbout(resourcePaths) | |
| .that(parsedAndWritten(source, fqn("id/exitId"))) | |
| .xmlContentsIsEqualTo(resourcesXmlFrom(source, "<item name='exitId' type='id'/>")); | |
| } | |
| @Test | |
| public void writeStringResourceWithXliffNamespace() throws Exception { | |
| Path source = fs.getPath("root/values/values.xml"); | |
| Files.createDirectories(source.getParent()); | |
| Files.write( | |
| source, | |
| ("<resources xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">" | |
| + "<string name=\"star_rating\">Check out our 5\n" | |
| + " <xliff:g id=\"star\">\\u2605</xliff:g>\n" | |
| + "</string>" | |
| + "</resources>") | |
| .getBytes(StandardCharsets.UTF_8)); | |
| assertAbout(resourcePaths) | |
| .that(parsedAndWritten(source, fqn("string/star_rating"))) | |
| .xmlContentsIsEqualTo( | |
| resourcesXmlFrom( | |
| XLIFF_NAMESPACES, | |
| ImmutableMap.<String, String>of(), | |
| source, | |
| "<string name=\"star_rating\">Check out our 5", | |
| " <xliff:g id=\"star\">\\u2605</xliff:g>", | |
| "</string>")); | |
| } | |
| @Test | |
| public void writeStringResourceCData() throws Exception { | |
| String[] xml = { | |
| "<string name='cdata'><![CDATA[<b>Jabber, Jabber</b><br><br>\n Wock!]]></string>" | |
| }; | |
| Path source = writeResourceXml(xml); | |
| assertAbout(resourcePaths) | |
| .that(parsedAndWritten(source, fqn("string/cdata"))) | |
| .xmlContentsIsEqualTo(resourcesXmlFrom(source, xml)); | |
| } | |
| @Test | |
| public void writeStringResourceWithNamespace() throws Exception { | |
| Path source = fs.getPath("root/values/values.xml"); | |
| Files.createDirectories(source.getParent()); | |
| Files.write( | |
| source, | |
| ("<resources xmlns:ns1=\"urn:oasis:names:tc:xliff:document:1.2\">" | |
| + "<string name=\"star_rating\">Check out our 5\n" | |
| + " <ns1:g xmlns:foglebert=\"defogle\" foglebert:id=\"star\">\\u2605</ns1:g>\n" | |
| + "</string>" | |
| + "</resources>") | |
| .getBytes(StandardCharsets.UTF_8)); | |
| assertAbout(resourcePaths) | |
| .that(parsedAndWritten(source, fqn("string/star_rating"))) | |
| .xmlContentsIsEqualTo( | |
| resourcesXmlFrom( | |
| ImmutableMap.of("ns1", "urn:oasis:names:tc:xliff:document:1.2"), | |
| ImmutableMap.<String, String>of(), | |
| source, | |
| "<string name=\"star_rating\">Check out our 5 ", | |
| "<ns1:g xmlns:foglebert=\"defogle\" " + "foglebert:id=\"star\">\\u2605</ns1:g>", | |
| "</string>")); | |
| } | |
| @Test | |
| public void writeStringResourceWithUnusedNamespace() throws Exception { | |
| Path source = fs.getPath("root/values/values.xml"); | |
| Files.createDirectories(source.getParent()); | |
| Files.write( | |
| source, | |
| ("<resources xmlns:ns1=\"urn:oasis:names:tc:xliff:document:1.2\">" | |
| + "<string name=\"star_rating\">" | |
| + "not yet implemented\n" | |
| + "</string>" | |
| + "</resources>") | |
| .getBytes(StandardCharsets.UTF_8)); | |
| assertAbout(resourcePaths) | |
| .that(parsedAndWritten(source, fqn("string/star_rating"))) | |
| .xmlContentsIsEqualTo( | |
| resourcesXmlFrom( | |
| source, "<string name=\"star_rating\">", "not yet implemented\n", "</string>")); | |
| } | |
| @Test | |
| public void writeStringResourceWithEscapedValues() throws Exception { | |
| String[] xml = { | |
| "<string name=\"AAP_SUGGEST_ACCEPT_SUGGESTION\">", | |
| " <b><xliff:g id=\"name\" example=\"Pizza hut\">%1$s</xliff:g></b> " | |
| + "already exists at <b><xliff:g id=\"address\" " | |
| + "example=\"123 main street\">%2$s</xliff:g></b><br>", | |
| " <br>", | |
| " Is this the place you\\'re trying to add?\n", | |
| "</string>" | |
| }; | |
| Path source = writeResourceXml(XLIFF_NAMESPACES, ImmutableMap.<String, String>of(), xml); | |
| assertAbout(resourcePaths) | |
| .that(parsedAndWritten(source, fqn("string/AAP_SUGGEST_ACCEPT_SUGGESTION"))) | |
| .xmlContentsIsEqualTo( | |
| resourcesXmlFrom(XLIFF_NAMESPACES, ImmutableMap.<String, String>of(), source, xml)); | |
| } | |
| @Test | |
| public void writeStyleableXmlResource() throws Exception { | |
| String[] xml = { | |
| "<declare-styleable name='Theme'>", | |
| " <attr name=\"labelPosition\" />", | |
| "</declare-styleable>" | |
| }; | |
| Path source = writeResourceXml(xml); | |
| assertAbout(resourcePaths) | |
| .that(parsedAndWritten(source, fqn("styleable/Theme"))) | |
| .xmlContentsIsEqualTo(resourcesXmlFrom(source, xml)); | |
| } | |
| @Test | |
| public void writeStyleableXmlResourceReference() throws Exception { | |
| String[] xml = { | |
| "<declare-styleable name='Theme'>", | |
| " <attr name=\"labelColor\" format=\"color\" />", | |
| "</declare-styleable>" | |
| }; | |
| Path source = writeResourceXml(xml); | |
| assertAbout(resourcePaths) | |
| .that(parsedAndWritten(source, fqn("styleable/Theme"), fqn("attr/labelColor"))) | |
| .xmlContentsIsEqualTo(resourcesXmlFrom(source, xml)); | |
| } | |
| @Test | |
| public void writePluralXmlResources() throws Exception { | |
| String[] xml = { | |
| "<plurals name='numberOfSongsAvailable' format='none'>", | |
| " <item quantity='one'>%d song found.</item>", | |
| " <item quantity='other'>%d songs found.</item>", | |
| "</plurals>" | |
| }; | |
| Path source = writeResourceXml(xml); | |
| assertAbout(resourcePaths) | |
| .that(parsedAndWritten(source, fqn("plurals/numberOfSongsAvailable"))) | |
| .xmlContentsIsEqualTo(resourcesXmlFrom(source, xml)); | |
| } | |
| @Test | |
| public void writePublicXmlResources() throws Exception { | |
| String[] xml = { | |
| "<public name='bar' type='dimen' id='0x7f030003' />", | |
| "<public name='foo' type='dimen' />", | |
| }; | |
| Path source = writeResourceXml(xml); | |
| assertAbout(resourcePaths) | |
| .that(parsedAndWritten(source, fqn("public/bar"), fqn("public/foo"))) | |
| .xmlContentsIsEqualTo(resourcesXmlFrom(source, xml)); | |
| } | |
| @Test | |
| public void writeArrayXmlResources() throws Exception { | |
| String[] xml = { | |
| "<array name='icons'>", | |
| " <item>@drawable/home</item>", | |
| " <item>@drawable/settings</item>", | |
| " <item>@drawable/logout</item>", | |
| "</array>", | |
| }; | |
| Path source = writeResourceXml(xml); | |
| assertAbout(resourcePaths) | |
| .that(parsedAndWritten(source, fqn("array/icons"))) | |
| .xmlContentsIsEqualTo(resourcesXmlFrom(source, xml)); | |
| } | |
| @Test | |
| public void writeStringArrayXmlResources() throws Exception { | |
| String[] xml = { | |
| "<string-array name='rosebud' translatable='false'>", | |
| " <item>Howard Hughes</item>", | |
| " <item>Randolph Hurst</item>", | |
| "</string-array>", | |
| }; | |
| Path source = writeResourceXml(xml); | |
| assertAbout(resourcePaths) | |
| .that(parsedAndWritten(source, fqn("array/rosebud"))) | |
| .xmlContentsIsEqualTo(resourcesXmlFrom(source, xml)); | |
| } | |
| @Test | |
| public void writeIntegerArrayXmlResources() throws Exception { | |
| String[] xml = { | |
| "<integer-array name='bits'>", | |
| " <item>4</item>", | |
| " <item>8</item>", | |
| " <item>16</item>", | |
| " <item>32</item>", | |
| "</integer-array>" | |
| }; | |
| Path source = writeResourceXml(xml); | |
| assertAbout(resourcePaths) | |
| .that(parsedAndWritten(source, fqn("array/bits"))) | |
| .xmlContentsIsEqualTo(resourcesXmlFrom(source, xml)); | |
| } | |
| @Test | |
| public void writeAttrFlagXmlResources() throws Exception { | |
| String[] xml = { | |
| " <attr name=\"labelPosition\">", | |
| " <flag name=\"left\" value=\"0\"/>", | |
| " <flag name=\"right\" value=\"1\"/>", | |
| " </attr>" | |
| }; | |
| Path source = writeResourceXml(xml); | |
| assertAbout(resourcePaths) | |
| .that(parsedAndWritten(source, fqn("attr/labelPosition"))) | |
| .xmlContentsIsEqualTo( | |
| resourcesXmlFrom( | |
| source, | |
| " <attr name=\"labelPosition\">", | |
| " <flag name=\"left\" value=\"0\"/>", | |
| " <flag name=\"right\" value=\"1\"/>", | |
| " </attr>")); | |
| } | |
| @Test | |
| public void writeAttrMultiFormatImplicitFlagXmlResources() throws Exception { | |
| String[] xml = { | |
| " <attr name='labelPosition' format='reference'>", | |
| " <flag name='left' value='0'/>", | |
| " <flag name='right' value='1'/>", | |
| " </attr>" | |
| }; | |
| Path source = writeResourceXml(xml); | |
| assertAbout(resourcePaths) | |
| .that(parsedAndWritten(source, fqn("attr/labelPosition"))) | |
| .xmlContentsIsEqualTo( | |
| resourcesXmlFrom( | |
| source, | |
| " <attr name='labelPosition' format='reference'>", | |
| " <flag name='left' value='0'/>", | |
| " <flag name='right' value='1'/>", | |
| " </attr>")); | |
| } | |
| @Test | |
| public void writeAttrMultiFormatResources() throws Exception { | |
| String[] xml = { | |
| "<attr name='labelPosition' ", | |
| "format='boolean|color|dimension|float|fraction|integer|reference|string'/>" | |
| }; | |
| Path source = writeResourceXml(xml); | |
| assertAbout(resourcePaths) | |
| .that(parsedAndWritten(source, fqn("attr/labelPosition"))) | |
| .xmlContentsIsEqualTo(resourcesXmlFrom(source, xml)); | |
| } | |
| @Test | |
| public void writeStyle() throws Exception { | |
| String[] xml = { | |
| "<style name='candlestick_maker' parent='@style/wax_maker'>\n" | |
| + " <item name='texture'>waxy</item>\n" | |
| + "</style>" | |
| }; | |
| Path source = writeResourceXml(xml); | |
| assertAbout(resourcePaths) | |
| .that(parsedAndWritten(source, fqn("style/candlestick_maker"))) | |
| .xmlContentsIsEqualTo(resourcesXmlFrom(source, xml)); | |
| } | |
| @Test | |
| public void writeForceNoParentStyle() throws Exception { | |
| String[] xml = { | |
| "<style name='candlestick_maker' parent=''>\n", | |
| " <item name='texture'>waxy</item>\n", | |
| "</style>" | |
| }; | |
| Path source = writeResourceXml(xml); | |
| assertAbout(resourcePaths) | |
| .that(parsedAndWritten(source, fqn("style/candlestick_maker"))) | |
| .xmlContentsIsEqualTo(resourcesXmlFrom(source, xml)); | |
| } | |
| @Test | |
| public void writeResourceAttributes() throws Exception { | |
| Path source = writeResourceXml( | |
| ImmutableMap.of("tools", "http://schemas.android.com/tools"), | |
| ImmutableMap.of("tools:foo", "fooVal")); | |
| assertAbout(resourcePaths) | |
| .that(parsedAndWritten(source, fqn("<resources>/{http://schemas.android.com/tools}foo"))) | |
| .xmlContentsIsEqualTo( | |
| resourcesXmlFrom( | |
| ImmutableMap.of("tools", "http://schemas.android.com/tools"), | |
| ImmutableMap.of("tools:foo", "fooVal"), | |
| null)); | |
| } | |
| @Test | |
| public void serializeMultipleSimpleXmlResources() throws Exception { | |
| Path serialized = fs.getPath("out/out.bin"); | |
| Path source = fs.getPath("res/values/values.xml"); | |
| FullyQualifiedName stringKey = fqn("string/exit"); | |
| DataResourceXml stringValue = | |
| DataResourceXml.createWithNoNamespace( | |
| source, | |
| SimpleXmlResourceValue.createWithValue(SimpleXmlResourceValue.Type.STRING, "way out")); | |
| FullyQualifiedName nullStringKey = fqn("string/nullexit"); | |
| DataResourceXml nullStringValue = | |
| DataResourceXml.createWithNoNamespace( | |
| source, | |
| SimpleXmlResourceValue.createWithValue(SimpleXmlResourceValue.Type.STRING, null)); | |
| FullyQualifiedName boolKey = fqn("bool/canExit"); | |
| DataResourceXml boolValue = | |
| DataResourceXml.createWithNoNamespace( | |
| source, | |
| SimpleXmlResourceValue.createWithValue(SimpleXmlResourceValue.Type.BOOL, "false")); | |
| FullyQualifiedName colorKey = fqn("color/exitColor"); | |
| DataResourceXml colorValue = | |
| DataResourceXml.createWithNoNamespace( | |
| source, | |
| SimpleXmlResourceValue.createWithValue(SimpleXmlResourceValue.Type.COLOR, "#FF000000")); | |
| FullyQualifiedName dimenKey = fqn("dimen/exitSize"); | |
| DataResourceXml dimenValue = | |
| DataResourceXml.createWithNoNamespace( | |
| source, | |
| SimpleXmlResourceValue.createWithValue(SimpleXmlResourceValue.Type.DIMEN, "20sp")); | |
| AndroidDataSerializer serializer = AndroidDataSerializer.create(); | |
| serializer.queueForSerialization(stringKey, stringValue); | |
| serializer.queueForSerialization(nullStringKey, nullStringValue); | |
| serializer.queueForSerialization(boolKey, boolValue); | |
| serializer.queueForSerialization(colorKey, colorValue); | |
| serializer.queueForSerialization(dimenKey, dimenValue); | |
| serializer.flushTo(serialized); | |
| AndroidDataDeserializer deserializer = AndroidParsedDataDeserializer.create(); | |
| final Map<DataKey, DataResource> toOverwrite = new HashMap<>(); | |
| final Map<DataKey, DataResource> toCombine = new HashMap<>(); | |
| deserializer.read( | |
| serialized, | |
| KeyValueConsumers.of(new FakeConsumer(toOverwrite), new FakeConsumer(toCombine), null)); | |
| assertThat(toOverwrite) | |
| .containsExactly( | |
| stringKey, stringValue, | |
| boolKey, boolValue, | |
| colorKey, colorValue, | |
| nullStringKey, nullStringValue, | |
| dimenKey, dimenValue); | |
| assertThat(toCombine).isEmpty(); | |
| } | |
| @Test | |
| public void serializeItemXmlResources() throws Exception { | |
| Path source = fs.getPath("res/values/values.xml"); | |
| assertSerialization( | |
| fqn("dimen/exitSizePercent"), | |
| DataResourceXml.createWithNoNamespace( | |
| source, SimpleXmlResourceValue.itemWithValue(ResourceType.DIMEN, "20%"))); | |
| assertSerialization( | |
| fqn("dimen/exitSizeFloat"), | |
| DataResourceXml.createWithNoNamespace( | |
| source, | |
| SimpleXmlResourceValue.itemWithFormattedValue(ResourceType.DIMEN, "float", "20.0"))); | |
| assertSerialization( | |
| fqn("fraction/denom"), | |
| DataResourceXml.createWithNoNamespace( | |
| source, | |
| SimpleXmlResourceValue.createWithValue(SimpleXmlResourceValue.Type.COLOR, "5%"))); | |
| assertSerialization( | |
| fqn("id/subtype_afrikaans"), | |
| DataResourceXml.createWithNoNamespace( | |
| source, SimpleXmlResourceValue.itemWithValue(ResourceType.ID, "0x6f972360"))); | |
| } | |
| @Test | |
| public void serializeStyleableXmlResource() throws Exception { | |
| Path serialized = fs.getPath("out/out.bin"); | |
| Path source = fs.getPath("res/values/values.xml"); | |
| FullyQualifiedName attrKey = fqn("attr/labelPosition"); | |
| DataResourceXml attrValue = | |
| DataResourceXml.createWithNoNamespace( | |
| source, | |
| AttrXmlResourceValue.fromFormatEntries( | |
| EnumResourceXmlAttrValue.asEntryOf("left", "0", "right", "1"))); | |
| FullyQualifiedName themeKey = fqn("styleable/Theme"); | |
| DataResourceXml themeValue = | |
| DataResourceXml.createWithNoNamespace( | |
| source, | |
| StyleableXmlResourceValue.createAllAttrAsReferences( | |
| fqnFactory.parse("attr/labelPosition"))); | |
| AndroidDataSerializer serializer = AndroidDataSerializer.create(); | |
| serializer.queueForSerialization(attrKey, attrValue); | |
| serializer.queueForSerialization(themeKey, themeValue); | |
| serializer.flushTo(serialized); | |
| AndroidDataDeserializer deserializer = AndroidParsedDataDeserializer.create(); | |
| final Map<DataKey, DataResource> toOverwrite = new HashMap<>(); | |
| final Map<DataKey, DataResource> toCombine = new HashMap<>(); | |
| deserializer.read( | |
| serialized, | |
| KeyValueConsumers.of(new FakeConsumer(toOverwrite), new FakeConsumer(toCombine), null)); | |
| assertThat(toOverwrite).containsEntry(attrKey, attrValue); | |
| assertThat(toCombine).containsEntry(themeKey, themeValue); | |
| } | |
| @Test | |
| public void serializePlurals() throws Exception { | |
| Path path = fs.getPath("res/values/values.xml"); | |
| assertSerialization( | |
| fqn("plurals/numberOfSongsAvailable"), | |
| DataResourceXml.createWithNoNamespace( | |
| path, | |
| PluralXmlResourceValue.createWithoutAttributes( | |
| ImmutableMap.of( | |
| "one", "%d song found.", | |
| "other", "%d songs found.")))); | |
| } | |
| @Test | |
| public void serializeArrays() throws Exception { | |
| Path path = fs.getPath("res/values/values.xml"); | |
| assertSerialization( | |
| fqn("plurals/numberOfSongsAvailable"), | |
| DataResourceXml.createWithNoNamespace( | |
| path, | |
| PluralXmlResourceValue.createWithoutAttributes( | |
| ImmutableMap.of( | |
| "one", "%d song found.", | |
| "other", "%d songs found.")))); | |
| assertSerialization( | |
| fqn("array/icons"), | |
| DataResourceXml.createWithNoNamespace( | |
| path, | |
| ArrayXmlResourceValue.of( | |
| ArrayType.ARRAY, "@drawable/home", "@drawable/settings", "@drawable/logout"))); | |
| assertSerialization( | |
| fqn("array/colors"), | |
| DataResourceXml.createWithNoNamespace( | |
| path, | |
| ArrayXmlResourceValue.of(ArrayType.ARRAY, "#FFFF0000", "#FF00FF00", "#FF0000FF"))); | |
| assertSerialization( | |
| fqn("array/characters"), | |
| DataResourceXml.createWithNoNamespace( | |
| path, | |
| ArrayXmlResourceValue.of( | |
| ArrayType.STRING_ARRAY, | |
| "boojum", | |
| "snark", | |
| "bellman", | |
| "barrister", | |
| "\\\"billiard-marker\\\""))); | |
| } | |
| @Test | |
| public void serializeAttrFlag() throws Exception { | |
| assertSerialization( | |
| fqn("attr/labelPosition"), | |
| DataResourceXml.createWithNoNamespace( | |
| fs.getPath("res/values/values.xml"), | |
| AttrXmlResourceValue.fromFormatEntries( | |
| FlagResourceXmlAttrValue.asEntryOf( | |
| "left", "0", | |
| "right", "1")))); | |
| } | |
| @Test | |
| public void serializeId() throws Exception { | |
| assertSerialization( | |
| fqn("id/squark"), | |
| DataResourceXml.createWithNoNamespace( | |
| fs.getPath("res/values/values.xml"), IdXmlResourceValue.of())); | |
| } | |
| @Test | |
| public void serializePublic() throws Exception { | |
| assertSerialization( | |
| fqn("public/park"), | |
| DataResourceXml.createWithNoNamespace( | |
| fs.getPath("res/values/public.xml"), PublicXmlResourceValue.of( | |
| ImmutableMap.of( | |
| ResourceType.DIMEN, Optional.of(0x7f040000), | |
| ResourceType.STRING, Optional.of(0x7f050000)) | |
| ))); | |
| } | |
| @Test | |
| public void serializeStyle() throws Exception { | |
| assertSerialization( | |
| fqn("style/snark"), | |
| DataResourceXml.createWithNoNamespace( | |
| fs.getPath("res/values/styles.xml"), | |
| StyleXmlResourceValue.of(null, ImmutableMap.of("look", "boojum")))); | |
| } | |
| @Test | |
| public void assertMultiFormatAttr() throws Exception { | |
| assertSerialization( | |
| fqn("attr/labelPosition"), | |
| DataResourceXml.createWithNoNamespace( | |
| fs.getPath("res/values/values.xml"), | |
| AttrXmlResourceValue.fromFormatEntries( | |
| ColorResourceXmlAttrValue.asEntry(), | |
| BooleanResourceXmlAttrValue.asEntry(), | |
| ReferenceResourceXmlAttrValue.asEntry(), | |
| DimensionResourceXmlAttrValue.asEntry(), | |
| FloatResourceXmlAttrValue.asEntry(), | |
| IntegerResourceXmlAttrValue.asEntry(), | |
| StringResourceXmlAttrValue.asEntry(), | |
| FractionResourceXmlAttrValue.asEntry()))); | |
| } | |
| private void assertSerialization(FullyQualifiedName key, DataValue value) throws Exception { | |
| Path serialized = fs.getPath("out/out.bin"); | |
| Files.deleteIfExists(serialized); | |
| Path manifestPath = fs.getPath("AndroidManifest.xml"); | |
| Files.deleteIfExists(manifestPath); | |
| Files.createFile(manifestPath); | |
| AndroidDataSerializer serializer = AndroidDataSerializer.create(); | |
| serializer.queueForSerialization(key, value); | |
| serializer.flushTo(serialized); | |
| AndroidDataDeserializer deserializer = AndroidParsedDataDeserializer.create(); | |
| final Map<DataKey, DataResource> toOverwrite = new HashMap<>(); | |
| final Map<DataKey, DataResource> toCombine = new HashMap<>(); | |
| deserializer.read( | |
| serialized, | |
| KeyValueConsumers.of(new FakeConsumer(toOverwrite), new FakeConsumer(toCombine), null)); | |
| if (key.isOverwritable()) { | |
| assertThat(toOverwrite).containsEntry(key, value); | |
| assertThat(toCombine).isEmpty(); | |
| } else { | |
| assertThat(toCombine).containsEntry(key, value); | |
| assertThat(toOverwrite).isEmpty(); | |
| } | |
| } | |
| private String[] resourcesXmlFrom(Path source, String... lines) { | |
| return resourcesXmlFrom(ImmutableMap.<String, String>of(), ImmutableMap.<String, String>of(), | |
| source, lines); | |
| } | |
| private String[] resourcesXmlFrom(Map<String, String> namespaces, Map<String, String> attributes, | |
| Path source, String... lines) { | |
| FluentIterable<String> xml = | |
| FluentIterable.of(new String(AndroidDataWriter.PRELUDE)) | |
| .append("<resources") | |
| .append( | |
| FluentIterable.from(namespaces.entrySet()) | |
| .transform( | |
| new Function<Map.Entry<String, String>, String>() { | |
| @Override | |
| public String apply(Map.Entry<String, String> input) { | |
| return String.format( | |
| " xmlns:%s=\"%s\"", input.getKey(), input.getValue()); | |
| } | |
| }) | |
| .join(Joiner.on(""))) | |
| .append( | |
| FluentIterable.from(attributes.entrySet()) | |
| .transform( | |
| new Function<Map.Entry<String, String>, String>() { | |
| @Override | |
| public String apply(Map.Entry<String, String> input) { | |
| return String.format(" %s=\"%s\"", input.getKey(), input.getValue()); | |
| } | |
| }) | |
| .join(Joiner.on(""))); | |
| if (source == null && (lines == null || lines.length == 0)) { | |
| xml = xml.append("/>"); | |
| } else { | |
| xml = xml.append(">"); | |
| if (source != null) { | |
| xml = xml.append(String.format("<!-- %s --> <eat-comment/>", source)); | |
| } | |
| if (lines != null) { | |
| xml = xml.append(lines); | |
| } | |
| xml = xml.append(END_RESOURCES); | |
| } | |
| return xml.toArray(String.class); | |
| } | |
| private Path parsedAndWritten(Path path, FullyQualifiedName... fqns) | |
| throws XMLStreamException, IOException, MergingException { | |
| final Map<DataKey, DataResource> toOverwrite = new HashMap<>(); | |
| final Map<DataKey, DataResource> toCombine = new HashMap<>(); | |
| parseResourcesFrom(path, toOverwrite, toCombine); | |
| Path out = fs.getPath("out"); | |
| if (Files.exists(out)) { | |
| Files.walkFileTree( | |
| out, | |
| new SimpleFileVisitor<Path>() { | |
| @Override | |
| public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) | |
| throws IOException { | |
| Files.delete(file); | |
| return FileVisitResult.CONTINUE; | |
| } | |
| @Override | |
| public FileVisitResult postVisitDirectory(Path directory, IOException e) | |
| throws IOException { | |
| if (e != null) { | |
| throw e; | |
| } | |
| Files.delete(directory); | |
| return FileVisitResult.CONTINUE; | |
| } | |
| }); | |
| } | |
| // find and write the resource -- the categorization is tested during parsing. | |
| AndroidDataWriter mergedDataWriter = AndroidDataWriter.createWithDefaults(out); | |
| for (FullyQualifiedName fqn : fqns) { | |
| if (toOverwrite.containsKey(fqn)) { | |
| toOverwrite.get(fqn).writeResource(fqn, mergedDataWriter); | |
| } else if (toCombine.containsKey(fqn)) { | |
| toCombine.get(fqn).writeResource(fqn, mergedDataWriter); | |
| } | |
| } | |
| mergedDataWriter.flush(); | |
| return mergedDataWriter.resourceDirectory().resolve("values/values.xml"); | |
| } | |
| private static final Subject.Factory<PathsSubject, Path> resourcePaths = PathsSubject::new; | |
| private static class FakeConsumer | |
| implements ParsedAndroidData.KeyValueConsumer<DataKey, DataResource> { | |
| private final Map<DataKey, DataResource> target; | |
| FakeConsumer(Map<DataKey, DataResource> target) { | |
| this.target = target; | |
| } | |
| @Override | |
| public void accept(DataKey key, DataResource value) { | |
| target.put(key, value); | |
| } | |
| } | |
| } |