1 /*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "process/ProductFilter.h"
18
19 #include <algorithm>
20
21 #include "ResourceTable.h"
22 #include "trace/TraceBuffer.h"
23
24 namespace aapt {
25
SelectProductToKeep(const ResourceNameRef & name,ResourceConfigValueIter begin,ResourceConfigValueIter end,android::IDiagnostics * diag)26 std::optional<ProductFilter::ResourceConfigValueIter> ProductFilter::SelectProductToKeep(
27 const ResourceNameRef& name, ResourceConfigValueIter begin, ResourceConfigValueIter end,
28 android::IDiagnostics* diag) {
29 ResourceConfigValueIter default_product_iter = end;
30 ResourceConfigValueIter selected_product_iter = end;
31
32 for (ResourceConfigValueIter iter = begin; iter != end; ++iter) {
33 ResourceConfigValue* config_value = iter->get();
34 if (products_.find(config_value->product) != products_.end()) {
35 if (selected_product_iter != end) {
36 // We have two possible values for this product!
37 diag->Error(android::DiagMessage(config_value->value->GetSource())
38 << "selection of product '" << config_value->product << "' for resource "
39 << name << " is ambiguous");
40
41 ResourceConfigValue* previously_selected_config_value = selected_product_iter->get();
42 diag->Note(android::DiagMessage(previously_selected_config_value->value->GetSource())
43 << "product '" << previously_selected_config_value->product
44 << "' is also a candidate");
45 return std::nullopt;
46 }
47
48 // Select this product.
49 selected_product_iter = iter;
50 }
51
52 if (config_value->product.empty() || config_value->product == "default") {
53 if (default_product_iter != end) {
54 // We have two possible default values.
55 diag->Error(android::DiagMessage(config_value->value->GetSource())
56 << "multiple default products defined for resource " << name);
57
58 ResourceConfigValue* previously_default_config_value = default_product_iter->get();
59 diag->Note(android::DiagMessage(previously_default_config_value->value->GetSource())
60 << "default product also defined here");
61 return std::nullopt;
62 }
63
64 // Mark the default.
65 default_product_iter = iter;
66 }
67 }
68
69 if (remove_default_config_values_) {
70 // If we are leaving only a specific product, return early here instead of selecting the default
71 // value. Returning end here will cause this value set to be skipped, and will be removed with
72 // ClearEmptyValues method.
73 return selected_product_iter;
74 }
75
76 if (default_product_iter == end) {
77 diag->Error(android::DiagMessage() << "no default product defined for resource " << name);
78 return std::nullopt;
79 }
80
81 if (selected_product_iter == end) {
82 selected_product_iter = default_product_iter;
83 }
84 return selected_product_iter;
85 }
86
Consume(IAaptContext * context,ResourceTable * table)87 bool ProductFilter::Consume(IAaptContext* context, ResourceTable* table) {
88 TRACE_NAME("ProductFilter::Consume");
89 bool error = false;
90 for (auto& pkg : table->packages) {
91 for (auto& type : pkg->types) {
92 for (auto& entry : type->entries) {
93 std::vector<std::unique_ptr<ResourceConfigValue>> new_values;
94
95 ResourceConfigValueIter iter = entry->values.begin();
96 ResourceConfigValueIter start_range_iter = iter;
97 while (iter != entry->values.end()) {
98 ++iter;
99 if (iter == entry->values.end() || (*iter)->config != (*start_range_iter)->config) {
100 // End of the array, or we saw a different config,
101 // so this must be the end of a range of products.
102 // Select the product to keep from the set of products defined.
103 ResourceNameRef name(pkg->name, type->named_type, entry->name);
104 auto value_to_keep =
105 SelectProductToKeep(name, start_range_iter, iter, context->GetDiagnostics());
106 if (!value_to_keep.has_value()) {
107 // An error occurred, we could not pick a product.
108 error = true;
109 } else if (auto val = value_to_keep.value(); val != iter) {
110 // We selected a product to keep. Move it to the new array.
111 if (remove_default_config_values_) {
112 // We are filtering values with the given product. The selected value here will be
113 // a new default value, and all other values will be removed.
114 new_values.push_back(
115 std::make_unique<ResourceConfigValue>((*val)->config, android::StringPiece{}));
116 new_values.back()->value = std::move((*val)->value);
117 } else {
118 new_values.push_back(std::move(*val));
119 }
120 }
121
122 // Start the next range of products.
123 start_range_iter = iter;
124 }
125 }
126
127 // Now move the new values in to place.
128 entry->values = std::move(new_values);
129 }
130 }
131 }
132
133 if (remove_default_config_values_) {
134 ClearEmptyValues(table);
135 }
136
137 return !error;
138 }
139
ClearEmptyValues(ResourceTable * table)140 void ProductFilter::ClearEmptyValues(ResourceTable* table) {
141 // Clear any empty packages/types/entries, as remove_default_config_values_ may remove an entire
142 // value set.
143 CHECK(remove_default_config_values_)
144 << __func__ << " should only be called when remove_default_config_values_ is set";
145
146 for (auto& pkg : table->packages) {
147 for (auto& type : pkg->types) {
148 std::erase_if(type->entries, [](auto& entry) { return entry->values.empty(); });
149 }
150 std::erase_if(pkg->types, [](auto& type) { return type->entries.empty(); });
151 }
152 std::erase_if(table->packages, [](auto& package) { return package->types.empty(); });
153 }
154
155 } // namespace aapt
156