| // 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); |
| } |
| } |
| } |