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 "ConfigDescription.h"
18#include "ResourceTable.h"
19#include "split/TableSplitter.h"
20
21#include <algorithm>
22#include <map>
23#include <set>
24#include <unordered_map>
25#include <vector>
26
27namespace aapt {
28
29using ConfigClaimedMap = std::unordered_map<ResourceConfigValue*, bool>;
30using ConfigDensityGroups = std::map<ConfigDescription, std::vector<ResourceConfigValue*>>;
31
32static ConfigDescription copyWithoutDensity(const ConfigDescription& config) {
33    ConfigDescription withoutDensity = config;
34    withoutDensity.density = 0;
35    return withoutDensity;
36}
37
38/**
39 * Selects values that match exactly the constraints given.
40 */
41class SplitValueSelector {
42public:
43    SplitValueSelector(const SplitConstraints& constraints) {
44        for (const ConfigDescription& config : constraints.configs) {
45            if (config.density == 0) {
46                mDensityIndependentConfigs.insert(config);
47            } else {
48                mDensityDependentConfigToDensityMap[copyWithoutDensity(config)] = config.density;
49            }
50        }
51    }
52
53    std::vector<ResourceConfigValue*> selectValues(const ConfigDensityGroups& densityGroups,
54                                                   ConfigClaimedMap* claimedValues) {
55        std::vector<ResourceConfigValue*> selected;
56
57        // Select the regular values.
58        for (auto& entry : *claimedValues) {
59            // Check if the entry has a density.
60            ResourceConfigValue* configValue = entry.first;
61            if (configValue->config.density == 0 && !entry.second) {
62                // This is still available.
63                if (mDensityIndependentConfigs.find(configValue->config) !=
64                        mDensityIndependentConfigs.end()) {
65                    selected.push_back(configValue);
66
67                    // Mark the entry as taken.
68                    entry.second = true;
69                }
70            }
71        }
72
73        // Now examine the densities
74        for (auto& entry : densityGroups) {
75            // We do not care if the value is claimed, since density values can be
76            // in multiple splits.
77            const ConfigDescription& config = entry.first;
78            const std::vector<ResourceConfigValue*>& relatedValues = entry.second;
79
80            auto densityValueIter = mDensityDependentConfigToDensityMap.find(config);
81            if (densityValueIter != mDensityDependentConfigToDensityMap.end()) {
82                // Select the best one!
83                ConfigDescription targetDensity = config;
84                targetDensity.density = densityValueIter->second;
85
86                ResourceConfigValue* bestValue = nullptr;
87                for (ResourceConfigValue* thisValue : relatedValues) {
88                    if (!bestValue ||
89                            thisValue->config.isBetterThan(bestValue->config, &targetDensity)) {
90                        bestValue = thisValue;
91                    }
92
93                    // When we select one of these, they are all claimed such that the base
94                    // doesn't include any anymore.
95                    (*claimedValues)[thisValue] = true;
96                }
97                assert(bestValue);
98                selected.push_back(bestValue);
99            }
100        }
101        return selected;
102    }
103
104private:
105    std::set<ConfigDescription> mDensityIndependentConfigs;
106    std::map<ConfigDescription, uint16_t> mDensityDependentConfigToDensityMap;
107};
108
109/**
110 * Marking non-preferred densities as claimed will make sure the base doesn't include them,
111 * leaving only the preferred density behind.
112 */
113static void markNonPreferredDensitiesAsClaimed(uint16_t preferredDensity,
114                                               const ConfigDensityGroups& densityGroups,
115                                               ConfigClaimedMap* configClaimedMap) {
116    for (auto& entry : densityGroups) {
117        const ConfigDescription& config = entry.first;
118        const std::vector<ResourceConfigValue*>& relatedValues = entry.second;
119
120        ConfigDescription targetDensity = config;
121        targetDensity.density = preferredDensity;
122        ResourceConfigValue* bestValue = nullptr;
123        for (ResourceConfigValue* thisValue : relatedValues) {
124            if (!bestValue) {
125                bestValue = thisValue;
126            } else if (thisValue->config.isBetterThan(bestValue->config, &targetDensity)) {
127                // Claim the previous value so that it is not included in the base.
128                (*configClaimedMap)[bestValue] = true;
129                bestValue = thisValue;
130            } else {
131                // Claim this value so that it is not included in the base.
132                (*configClaimedMap)[thisValue] = true;
133            }
134        }
135        assert(bestValue);
136    }
137}
138
139bool TableSplitter::verifySplitConstraints(IAaptContext* context) {
140    bool error = false;
141    for (size_t i = 0; i < mSplitConstraints.size(); i++) {
142        for (size_t j = i + 1; j < mSplitConstraints.size(); j++) {
143            for (const ConfigDescription& config : mSplitConstraints[i].configs) {
144                if (mSplitConstraints[j].configs.find(config) !=
145                        mSplitConstraints[j].configs.end()) {
146                    context->getDiagnostics()->error(DiagMessage() << "config '" << config
147                                                     << "' appears in multiple splits, "
148                                                     << "target split ambiguous");
149                    error = true;
150                }
151            }
152        }
153    }
154    return !error;
155}
156
157void TableSplitter::splitTable(ResourceTable* originalTable) {
158    const size_t splitCount = mSplitConstraints.size();
159    for (auto& pkg : originalTable->packages) {
160        // Initialize all packages for splits.
161        for (size_t idx = 0; idx < splitCount; idx++) {
162            ResourceTable* splitTable = mSplits[idx].get();
163            splitTable->createPackage(pkg->name, pkg->id);
164        }
165
166        for (auto& type : pkg->types) {
167            if (type->type == ResourceType::kMipmap) {
168                // Always keep mipmaps.
169                continue;
170            }
171
172            for (auto& entry : type->entries) {
173                if (mConfigFilter) {
174                    // First eliminate any resource that we definitely don't want.
175                    for (std::unique_ptr<ResourceConfigValue>& configValue : entry->values) {
176                        if (!mConfigFilter->match(configValue->config)) {
177                            // null out the entry. We will clean up and remove nulls at the end
178                            // for performance reasons.
179                            configValue.reset();
180                        }
181                    }
182                }
183
184                // Organize the values into two separate buckets. Those that are density-dependent
185                // and those that are density-independent.
186                // One density technically matches all density, it's just that some densities
187                // match better. So we need to be aware of the full set of densities to make this
188                // decision.
189                ConfigDensityGroups densityGroups;
190                ConfigClaimedMap configClaimedMap;
191                for (const std::unique_ptr<ResourceConfigValue>& configValue : entry->values) {
192                    if (configValue) {
193                        configClaimedMap[configValue.get()] = false;
194
195                        if (configValue->config.density != 0) {
196                            // Create a bucket for this density-dependent config.
197                            densityGroups[copyWithoutDensity(configValue->config)]
198                                          .push_back(configValue.get());
199                        }
200                    }
201                }
202
203                // First we check all the splits. If it doesn't match one of the splits, we
204                // leave it in the base.
205                for (size_t idx = 0; idx < splitCount; idx++) {
206                    const SplitConstraints& splitConstraint = mSplitConstraints[idx];
207                    ResourceTable* splitTable = mSplits[idx].get();
208
209                    // Select the values we want from this entry for this split.
210                    SplitValueSelector selector(splitConstraint);
211                    std::vector<ResourceConfigValue*> selectedValues =
212                            selector.selectValues(densityGroups, &configClaimedMap);
213
214                    // No need to do any work if we selected nothing.
215                    if (!selectedValues.empty()) {
216                        // Create the same resource structure in the split. We do this lazily
217                        // because we might not have actual values for each type/entry.
218                        ResourceTablePackage* splitPkg = splitTable->findPackage(pkg->name);
219                        ResourceTableType* splitType = splitPkg->findOrCreateType(type->type);
220                        if (!splitType->id) {
221                            splitType->id = type->id;
222                            splitType->symbolStatus = type->symbolStatus;
223                        }
224
225                        ResourceEntry* splitEntry = splitType->findOrCreateEntry(entry->name);
226                        if (!splitEntry->id) {
227                            splitEntry->id = entry->id;
228                            splitEntry->symbolStatus = entry->symbolStatus;
229                        }
230
231                        // Copy the selected values into the new Split Entry.
232                        for (ResourceConfigValue* configValue : selectedValues) {
233                            ResourceConfigValue* newConfigValue = splitEntry->findOrCreateValue(
234                                    configValue->config, configValue->product);
235                            newConfigValue->value = std::unique_ptr<Value>(
236                                    configValue->value->clone(&splitTable->stringPool));
237                        }
238                    }
239                }
240
241                if (mPreferredDensity) {
242                    markNonPreferredDensitiesAsClaimed(mPreferredDensity.value(),
243                                                       densityGroups,
244                                                       &configClaimedMap);
245                }
246
247                // All splits are handled, now check to see what wasn't claimed and remove
248                // whatever exists in other splits.
249                for (std::unique_ptr<ResourceConfigValue>& configValue : entry->values) {
250                    if (configValue && configClaimedMap[configValue.get()]) {
251                        // Claimed, remove from base.
252                        configValue.reset();
253                    }
254                }
255
256                // Now erase all nullptrs.
257                entry->values.erase(
258                        std::remove(entry->values.begin(), entry->values.end(), nullptr),
259                        entry->values.end());
260            }
261        }
262    }
263}
264
265} // namespace aapt
266