blob: 875288cef6e0e6b3cc9922991045352dbe1ca18e [file] [log] [blame]
Rico Windf9c505d2023-08-09 15:41:46 +02001// Copyright (c) 2023, the R8 project authors. Please see the AUTHORS file
2// for details. All rights reserved. Use of this source code is governed by a
3// BSD-style license that can be found in the LICENSE file.
Rico Windf9c505d2023-08-09 15:41:46 +02004package com.android.build.shrinker.r8integration;
5
Rico Wind717e9732024-02-09 10:36:00 +01006import static com.android.build.shrinker.r8integration.LegacyResourceShrinker.getUtfReader;
7
Rico Windd773abd2024-02-19 11:25:18 +01008import com.android.aapt.Resources;
Rico Windf4991842023-11-23 08:00:15 +01009import com.android.aapt.Resources.ConfigValue;
10import com.android.aapt.Resources.Entry;
Rico Windd773abd2024-02-19 11:25:18 +010011import com.android.aapt.Resources.FileReference;
Rico Windf4991842023-11-23 08:00:15 +010012import com.android.aapt.Resources.Item;
Rico Windd773abd2024-02-19 11:25:18 +010013import com.android.aapt.Resources.Package;
Rico Windf9c505d2023-08-09 15:41:46 +020014import com.android.aapt.Resources.ResourceTable;
Rico Windf4991842023-11-23 08:00:15 +010015import com.android.aapt.Resources.Value;
Rico Windd773abd2024-02-19 11:25:18 +010016import com.android.aapt.Resources.XmlAttribute;
17import com.android.aapt.Resources.XmlElement;
Rico Wind717e9732024-02-09 10:36:00 +010018import com.android.aapt.Resources.XmlNode;
Rico Wind717e9732024-02-09 10:36:00 +010019import com.android.build.shrinker.ResourceShrinkerImplKt;
Rico Windf9c505d2023-08-09 15:41:46 +020020import com.android.build.shrinker.ResourceShrinkerModel;
21import com.android.build.shrinker.ResourceTableUtilKt;
22import com.android.build.shrinker.ShrinkerDebugReporter;
Rico Wind717e9732024-02-09 10:36:00 +010023import com.android.build.shrinker.graph.ProtoResourcesGraphBuilder;
24import com.android.build.shrinker.r8integration.LegacyResourceShrinker.ShrinkerResult;
25import com.android.build.shrinker.usages.ProtoAndroidManifestUsageRecorderKt;
26import com.android.build.shrinker.usages.ToolsAttributeUsageRecorderKt;
Rico Windf9c505d2023-08-09 15:41:46 +020027import com.android.ide.common.resources.ResourcesUtil;
Rico Wind717e9732024-02-09 10:36:00 +010028import com.android.ide.common.resources.usage.ResourceStore;
Rico Windf9c505d2023-08-09 15:41:46 +020029import com.android.ide.common.resources.usage.ResourceUsageModel;
30import com.android.ide.common.resources.usage.ResourceUsageModel.Resource;
31import com.android.resources.ResourceType;
Rico Wind717e9732024-02-09 10:36:00 +010032import com.android.tools.r8.FeatureSplit;
Rico Windd773abd2024-02-19 11:25:18 +010033import com.android.tools.r8.origin.Origin;
34import com.android.tools.r8.origin.PathOrigin;
Rico Wind717e9732024-02-09 10:36:00 +010035import com.google.common.collect.ImmutableSet;
36import com.google.common.collect.Iterables;
Rico Windf9c505d2023-08-09 15:41:46 +020037import java.io.IOException;
38import java.io.InputStream;
Rico Windd773abd2024-02-19 11:25:18 +010039import java.nio.file.Paths;
Rico Wind1a9fee02024-02-20 11:24:25 +010040import java.util.ArrayList;
Rico Windf4991842023-11-23 08:00:15 +010041import java.util.HashMap;
Rico Windd773abd2024-02-19 11:25:18 +010042import java.util.HashSet;
Rico Wind717e9732024-02-09 10:36:00 +010043import java.util.IdentityHashMap;
Rico Windf9c505d2023-08-09 15:41:46 +020044import java.util.List;
Rico Windf4991842023-11-23 08:00:15 +010045import java.util.Map;
Rico Windd773abd2024-02-19 11:25:18 +010046import java.util.Set;
Rico Wind717e9732024-02-09 10:36:00 +010047import java.util.function.Function;
48import java.util.function.Supplier;
49import java.util.stream.Collectors;
Rico Windf9c505d2023-08-09 15:41:46 +020050
51public class R8ResourceShrinkerState {
Rico Wind8907dd02023-11-14 13:54:13 +010052
Rico Wind717e9732024-02-09 10:36:00 +010053 private final Function<Exception, RuntimeException> errorHandler;
54 private final R8ResourceShrinkerModel r8ResourceShrinkerModel;
55 private final Map<String, Supplier<InputStream>> xmlFileProviders = new HashMap<>();
Rico Windfd460d22024-08-20 11:06:18 +020056 private final List<Supplier<InputStream>> keepRuleFileProviders = new ArrayList<>();
Rico Wind717e9732024-02-09 10:36:00 +010057
Rico Wind1a9fee02024-02-20 11:24:25 +010058 private final List<Supplier<InputStream>> manifestProviders = new ArrayList<>();
Rico Wind717e9732024-02-09 10:36:00 +010059 private final Map<String, Supplier<InputStream>> resfileProviders = new HashMap<>();
60 private final Map<ResourceTable, FeatureSplit> resourceTables = new HashMap<>();
Rico Windd773abd2024-02-19 11:25:18 +010061 private ClassReferenceCallback enqueuerCallback;
Rico Wind5b0579f2024-08-13 08:46:59 +020062 private Map<Integer, List<String>> resourceIdToXmlFiles;
Rico Windd773abd2024-02-19 11:25:18 +010063 private Set<String> packageNames;
64 private final Set<String> seenNoneClassValues = new HashSet<>();
65 private final Set<Integer> seenResourceIds = new HashSet<>();
66
Rico Wind5f9bf272024-08-28 13:38:10 +020067 private static final Set<String> SPECIAL_MANIFEST_ELEMENTS =
68 ImmutableSet.of(
69 "provider",
70 "activity",
71 "service",
72 "receiver",
73 "instrumentation",
74 "process",
75 "application");
76
77 private static final Set<String> SPECIAL_APPLICATION_ATTRIBUTES =
78 ImmutableSet.of("backupAgent", "appComponentFactory", "zygotePreloadName");
79
Rico Windd773abd2024-02-19 11:25:18 +010080 @FunctionalInterface
81 public interface ClassReferenceCallback {
82 boolean tryClass(String possibleClass, Origin xmlFileOrigin);
83 }
Rico Wind717e9732024-02-09 10:36:00 +010084
Rico Windc5b44012024-08-16 13:30:59 +020085 public R8ResourceShrinkerState(
86 Function<Exception, RuntimeException> errorHandler,
87 ShrinkerDebugReporter shrinkerDebugReporter) {
88 r8ResourceShrinkerModel = new R8ResourceShrinkerModel(shrinkerDebugReporter, true);
Rico Wind717e9732024-02-09 10:36:00 +010089 this.errorHandler = errorHandler;
90 }
Rico Windf9c505d2023-08-09 15:41:46 +020091
Rico Windd773abd2024-02-19 11:25:18 +010092 public void trace(int id) {
93 if (!seenResourceIds.add(id)) {
94 return;
95 }
Rico Windf9c505d2023-08-09 15:41:46 +020096 Resource resource = r8ResourceShrinkerModel.getResourceStore().getResource(id);
Rico Wind717e9732024-02-09 10:36:00 +010097 if (resource == null) {
Rico Windd773abd2024-02-19 11:25:18 +010098 return;
Rico Wind717e9732024-02-09 10:36:00 +010099 }
Rico Windf9c505d2023-08-09 15:41:46 +0200100 ResourceUsageModel.markReachable(resource);
Rico Wind1a9fee02024-02-20 11:24:25 +0100101 traceXmlForResourceId(id);
Rico Wind717e9732024-02-09 10:36:00 +0100102 if (resource.references != null) {
103 for (Resource reference : resource.references) {
104 if (!reference.isReachable()) {
105 trace(reference.value);
106 }
107 }
108 }
Rico Windd773abd2024-02-19 11:25:18 +0100109 }
110
Rico Wind80ea3012024-03-14 11:28:09 +0100111 public void traceKeepXmlAndManifest() {
112 // We start by building the root set of all keep/discard rules to find those pinned resources
113 // before marking additional resources in the trace.
114 // We then explicitly trace those resources to transitively get the full set of reachable
115 // resources and code.
116 try {
117 updateModelWithKeepXmlReferences();
118 } catch (IOException e) {
119 throw errorHandler.apply(e);
120 }
Rico Wind7e818802024-04-10 14:29:43 +0200121 // ProcessToolsAttribute returns the resources that becomes live
122 r8ResourceShrinkerModel
123 .getResourceStore()
124 .processToolsAttributes()
125 .forEach(resource -> trace(resource.value));
Rico Wind1a9fee02024-02-20 11:24:25 +0100126 for (Supplier<InputStream> manifestProvider : manifestProviders) {
127 traceXml("AndroidManifest.xml", manifestProvider.get());
128 }
129 }
130
Rico Windd773abd2024-02-19 11:25:18 +0100131 public void setEnqueuerCallback(ClassReferenceCallback enqueuerCallback) {
132 assert this.enqueuerCallback == null;
133 this.enqueuerCallback = enqueuerCallback;
134 }
135
136 private synchronized Set<String> getPackageNames() {
137 // TODO(b/325888516): Consider only doing this for the package corresponding to the current
138 // feature.
139 if (packageNames == null) {
140 packageNames = new HashSet<>();
141 for (ResourceTable resourceTable : resourceTables.keySet()) {
142 for (Package aPackage : resourceTable.getPackageList()) {
143 packageNames.add(aPackage.getPackageName());
144 }
145 }
146 }
147 return packageNames;
Rico Windf9c505d2023-08-09 15:41:46 +0200148 }
149
Rico Wind1a9fee02024-02-20 11:24:25 +0100150 public void addManifestProvider(Supplier<InputStream> manifestProvider) {
151 this.manifestProviders.add(manifestProvider);
Rico Wind717e9732024-02-09 10:36:00 +0100152 }
153
154 public void addXmlFileProvider(Supplier<InputStream> inputStreamSupplier, String location) {
155 this.xmlFileProviders.put(location, inputStreamSupplier);
156 }
157
Rico Windfd460d22024-08-20 11:06:18 +0200158 public void addKeepRuleRileProvider(Supplier<InputStream> inputStreamSupplier) {
159 this.keepRuleFileProviders.add(inputStreamSupplier);
160 }
161
Rico Wind717e9732024-02-09 10:36:00 +0100162 public void addResFileProvider(Supplier<InputStream> inputStreamSupplier, String location) {
163 this.resfileProviders.put(location, inputStreamSupplier);
164 }
165
166 public void addResourceTable(InputStream inputStream, FeatureSplit featureSplit) {
167 this.resourceTables.put(
168 r8ResourceShrinkerModel.instantiateFromResourceTable(inputStream, true), featureSplit);
Rico Windf9c505d2023-08-09 15:41:46 +0200169 }
170
Rico Wind8907dd02023-11-14 13:54:13 +0100171 public R8ResourceShrinkerModel getR8ResourceShrinkerModel() {
172 return r8ResourceShrinkerModel;
173 }
174
Rico Wind717e9732024-02-09 10:36:00 +0100175 private byte[] getXmlOrResFileBytes(String path) {
176 assert !path.startsWith("res/");
177 String pathWithRes = "res/" + path;
178 Supplier<InputStream> inputStreamSupplier = xmlFileProviders.get(pathWithRes);
179 if (inputStreamSupplier == null) {
180 inputStreamSupplier = resfileProviders.get(pathWithRes);
181 }
182 if (inputStreamSupplier == null) {
183 // Ill formed resource table with file references inside res/ that does not exist.
184 return null;
185 }
186 try {
187 return inputStreamSupplier.get().readAllBytes();
188 } catch (IOException ex) {
189 throw errorHandler.apply(ex);
190 }
191 }
192
193 public void setupReferences() {
194 for (ResourceTable resourceTable : resourceTables.keySet()) {
195 new ProtoResourcesGraphBuilder(this::getXmlOrResFileBytes, unused -> resourceTable)
196 .buildGraph(r8ResourceShrinkerModel);
197 }
198 }
199
200 public ShrinkerResult shrinkModel() throws IOException {
201 updateModelWithManifestReferences();
202 updateModelWithKeepXmlReferences();
203 ResourceStore resourceStore = r8ResourceShrinkerModel.getResourceStore();
204 resourceStore.processToolsAttributes();
205 ImmutableSet<String> resEntriesToKeep = getResEntriesToKeep(resourceStore);
206 List<Integer> resourceIdsToRemove = getResourcesToRemove();
207
208 Map<FeatureSplit, ResourceTable> shrunkenTables = new IdentityHashMap<>();
209 resourceTables.forEach(
210 (resourceTable, featureSplit) ->
211 shrunkenTables.put(
212 featureSplit,
Rico Wind28f4d622024-02-20 13:54:46 +0100213 ResourceTableUtilKt.nullOutEntriesWithIds(
214 resourceTable, resourceIdsToRemove, true)));
Rico Wind717e9732024-02-09 10:36:00 +0100215
216 return new ShrinkerResult(resEntriesToKeep, shrunkenTables);
217 }
218
219 private ImmutableSet<String> getResEntriesToKeep(ResourceStore resourceStore) {
220 ImmutableSet.Builder<String> resEntriesToKeep = new ImmutableSet.Builder<>();
221 for (String path : Iterables.concat(xmlFileProviders.keySet(), resfileProviders.keySet())) {
222 if (ResourceShrinkerImplKt.isJarPathReachable(resourceStore, path)) {
223 resEntriesToKeep.add(path);
224 }
225 }
226 return resEntriesToKeep.build();
227 }
228
Rico Wind1a9fee02024-02-20 11:24:25 +0100229 private void traceXmlForResourceId(int id) {
Rico Wind5b0579f2024-08-13 08:46:59 +0200230 List<String> xmlFiles = getResourceIdToXmlFiles().get(id);
231 if (xmlFiles != null) {
232 for (String xmlFile : xmlFiles) {
233 InputStream inputStream = xmlFileProviders.get(xmlFile).get();
234 traceXml(xmlFile, inputStream);
235 }
Rico Windd773abd2024-02-19 11:25:18 +0100236 }
237 }
238
Rico Wind1a9fee02024-02-20 11:24:25 +0100239 private void traceXml(String xmlFile, InputStream inputStream) {
240 try {
241 XmlNode xmlNode = XmlNode.parseFrom(inputStream);
Rico Wind5f9bf272024-08-28 13:38:10 +0200242 visitNode(xmlNode, xmlFile, null);
Rico Winda999ea82024-06-19 12:10:40 +0200243 // Ensure that we trace the transitive reachable ids, without us having to iterate all
244 // resources for the reachable marker.
245 ProtoAndroidManifestUsageRecorderKt.recordUsagesFromNode(xmlNode, r8ResourceShrinkerModel)
246 .iterator()
247 .forEachRemaining(resource -> trace(resource.value));
Rico Wind1a9fee02024-02-20 11:24:25 +0100248 } catch (IOException e) {
249 errorHandler.apply(e);
250 }
251 }
252
Rico Windd773abd2024-02-19 11:25:18 +0100253 private void tryEnqueuerOnString(String possibleClass, String xmlName) {
254 // There are a lot of xml tags and attributes that are evaluated over and over, if it is
255 // not a class, ignore it.
256 if (seenNoneClassValues.contains(possibleClass)) {
257 return;
258 }
259 if (!enqueuerCallback.tryClass(possibleClass, new PathOrigin(Paths.get(xmlName)))) {
260 seenNoneClassValues.add(possibleClass);
261 }
262 }
263
Rico Wind5f9bf272024-08-28 13:38:10 +0200264 private void visitNode(XmlNode xmlNode, String xmlName, String manifestPackageName) {
Rico Windd773abd2024-02-19 11:25:18 +0100265 XmlElement element = xmlNode.getElement();
266 tryEnqueuerOnString(element.getName(), xmlName);
Rico Wind5f9bf272024-08-28 13:38:10 +0200267
Rico Windd773abd2024-02-19 11:25:18 +0100268 for (XmlAttribute xmlAttribute : element.getAttributeList()) {
Rico Wind5f9bf272024-08-28 13:38:10 +0200269 if (xmlAttribute.getName().equals("package") && element.getName().equals("manifest")) {
270 // We are traversing a manifest, record the package name if we see it.
271 manifestPackageName = xmlAttribute.getValue();
272 }
Rico Windd773abd2024-02-19 11:25:18 +0100273 String value = xmlAttribute.getValue();
274 tryEnqueuerOnString(value, xmlName);
275 if (value.startsWith(".")) {
276 // package specific names, e.g. context
277 getPackageNames().forEach(s -> tryEnqueuerOnString(s + value, xmlName));
278 }
Rico Wind5f9bf272024-08-28 13:38:10 +0200279 if (manifestPackageName != null) {
280 // Manifest case
281 traceManifestSpecificValues(xmlName, manifestPackageName, xmlAttribute, element);
282 }
Rico Windd773abd2024-02-19 11:25:18 +0100283 }
Rico Wind5f9bf272024-08-28 13:38:10 +0200284 for (XmlNode node : element.getChildList()) {
285 visitNode(node, xmlName, manifestPackageName);
286 }
287 }
288
289 private void traceManifestSpecificValues(
290 String xmlName, String packageName, XmlAttribute xmlAttribute, XmlElement element) {
291 if (!SPECIAL_MANIFEST_ELEMENTS.contains(element.getName())) {
292 return;
293 }
294 // All elements can have package specific name attributes pointing at classes.
295 if (xmlAttribute.getName().equals("name")) {
296 tryEnqueuerOnString(getFullyQualifiedName(packageName, xmlAttribute), xmlName);
297 }
298 // Application elements have multiple special case attributes, where the value is potentially
299 // a class name (unqualified).
300 if (element.getName().equals("application")) {
301 if (SPECIAL_APPLICATION_ATTRIBUTES.contains(xmlAttribute.getName())) {
302 tryEnqueuerOnString(getFullyQualifiedName(packageName, xmlAttribute), xmlName);
303 }
304 }
305 }
306
307 private static String getFullyQualifiedName(String packageName, XmlAttribute xmlAttribute) {
308 return packageName + "." + xmlAttribute.getValue();
Rico Windd773abd2024-02-19 11:25:18 +0100309 }
310
Rico Wind5b0579f2024-08-13 08:46:59 +0200311 public Map<Integer, List<String>> getResourceIdToXmlFiles() {
Rico Windd773abd2024-02-19 11:25:18 +0100312 if (resourceIdToXmlFiles == null) {
313 resourceIdToXmlFiles = new HashMap<>();
314 for (ResourceTable resourceTable : resourceTables.keySet()) {
315 for (Package packageEntry : resourceTable.getPackageList()) {
316 for (Resources.Type type : packageEntry.getTypeList()) {
317 for (Entry entry : type.getEntryList()) {
318 for (ConfigValue configValue : entry.getConfigValueList()) {
319 if (configValue.hasValue()) {
320 Value value = configValue.getValue();
321 if (value.hasItem()) {
322 Item item = value.getItem();
323 if (item.hasFile()) {
324 FileReference file = item.getFile();
325 if (file.getType() == FileReference.Type.PROTO_XML) {
326 int id = ResourceTableUtilKt.toIdentifier(packageEntry, type, entry);
Rico Wind5b0579f2024-08-13 08:46:59 +0200327 resourceIdToXmlFiles
328 .computeIfAbsent(id, unused -> new ArrayList<>())
329 .add(file.getPath());
Rico Windd773abd2024-02-19 11:25:18 +0100330 }
331 }
332 }
333 }
334 }
335 }
336 }
337 }
338 }
339 }
340 return resourceIdToXmlFiles;
341 }
342
Rico Wind717e9732024-02-09 10:36:00 +0100343 private List<Integer> getResourcesToRemove() {
344 return r8ResourceShrinkerModel.getResourceStore().getResources().stream()
345 .filter(r -> !r.isReachable() && !r.isPublic())
346 .filter(r -> r.type != ResourceType.ID)
347 .map(r -> r.value)
348 .collect(Collectors.toList());
349 }
350
351 // Temporary to support updating the reachable entries from the manifest, we need to instead
352 // trace these in the enqueuer.
353 public void updateModelWithManifestReferences() throws IOException {
Rico Wind1a9fee02024-02-20 11:24:25 +0100354 for (Supplier<InputStream> manifestProvider : manifestProviders) {
355 ProtoAndroidManifestUsageRecorderKt.recordUsagesFromNode(
356 XmlNode.parseFrom(manifestProvider.get()), r8ResourceShrinkerModel);
Rico Wind717e9732024-02-09 10:36:00 +0100357 }
Rico Wind717e9732024-02-09 10:36:00 +0100358 }
359
360 public void updateModelWithKeepXmlReferences() throws IOException {
Rico Windfd460d22024-08-20 11:06:18 +0200361 for (Supplier<InputStream> keepRuleFileProvider : keepRuleFileProviders) {
362 ToolsAttributeUsageRecorderKt.processRawXml(
363 getUtfReader(keepRuleFileProvider.get().readAllBytes()), r8ResourceShrinkerModel);
Rico Wind717e9732024-02-09 10:36:00 +0100364 }
365 }
366
Rico Windd773abd2024-02-19 11:25:18 +0100367 public void enqueuerDone(boolean isFinalTreeshaking) {
368 enqueuerCallback = null;
369 seenResourceIds.clear();
370 if (!isFinalTreeshaking) {
371 // After final tree shaking we will need the reachability bits to decide what to write out
372 // from the model.
373 clearReachableBits();
374 }
375 }
376
Rico Wind717e9732024-02-09 10:36:00 +0100377 public void clearReachableBits() {
378 for (Resource resource : r8ResourceShrinkerModel.getResourceStore().getResources()) {
379 resource.setReachable(false);
380 }
381 }
382
Rico Wind441f03d2023-10-26 12:40:07 +0200383 public static class R8ResourceShrinkerModel extends ResourceShrinkerModel {
Rico Windf4991842023-11-23 08:00:15 +0100384 private final Map<Integer, String> stringResourcesWithSingleValue = new HashMap<>();
Rico Windf9c505d2023-08-09 15:41:46 +0200385
386 public R8ResourceShrinkerModel(
387 ShrinkerDebugReporter debugReporter, boolean supportMultipackages) {
388 super(debugReporter, supportMultipackages);
389 }
390
Rico Wind342726c2024-01-30 16:51:18 +0100391 public String getSingleStringValueOrNull(int id) {
392 return stringResourcesWithSingleValue.get(id);
Rico Windf4991842023-11-23 08:00:15 +0100393 }
394
Rico Windf9c505d2023-08-09 15:41:46 +0200395 // Similar to instantiation in ProtoResourceTableGatherer, but using an inputstream.
Rico Wind717e9732024-02-09 10:36:00 +0100396 ResourceTable instantiateFromResourceTable(InputStream inputStream, boolean includeStyleables) {
Rico Windf9c505d2023-08-09 15:41:46 +0200397 try {
398 ResourceTable resourceTable = ResourceTable.parseFrom(inputStream);
Rico Windba8a9a42024-01-15 11:22:01 +0100399 instantiateFromResourceTable(resourceTable, includeStyleables);
Rico Wind717e9732024-02-09 10:36:00 +0100400 return resourceTable;
Rico Windf9c505d2023-08-09 15:41:46 +0200401 } catch (IOException ex) {
402 throw new RuntimeException(ex);
403 }
404 }
Rico Wind441f03d2023-10-26 12:40:07 +0200405
Rico Windba8a9a42024-01-15 11:22:01 +0100406 void instantiateFromResourceTable(ResourceTable resourceTable, boolean includeStyleables) {
Rico Wind441f03d2023-10-26 12:40:07 +0200407 ResourceTableUtilKt.entriesSequence(resourceTable)
408 .iterator()
409 .forEachRemaining(
410 entryWrapper -> {
411 ResourceType resourceType = ResourceType.fromClassName(entryWrapper.getType());
Rico Windf4991842023-11-23 08:00:15 +0100412 Entry entry = entryWrapper.getEntry();
413 int entryId = entryWrapper.getId();
414 recordSingleValueResources(resourceType, entry, entryId);
Rico Windba8a9a42024-01-15 11:22:01 +0100415 if (resourceType != ResourceType.STYLEABLE || includeStyleables) {
Rico Wind441f03d2023-10-26 12:40:07 +0200416 this.addResource(
417 resourceType,
418 entryWrapper.getPackageName(),
Rico Windf4991842023-11-23 08:00:15 +0100419 ResourcesUtil.resourceNameToFieldName(entry.getName()),
420 entryId);
Rico Wind441f03d2023-10-26 12:40:07 +0200421 }
422 });
423 }
Rico Windf4991842023-11-23 08:00:15 +0100424
425 private void recordSingleValueResources(ResourceType resourceType, Entry entry, int entryId) {
426 if (!entry.hasOverlayableItem() && entry.getConfigValueList().size() == 1) {
427 if (resourceType == ResourceType.STRING) {
428 ConfigValue configValue = entry.getConfigValue(0);
429 if (configValue.hasValue()) {
430 Value value = configValue.getValue();
431 if (value.hasItem()) {
432 Item item = value.getItem();
433 if (item.hasStr()) {
434 stringResourcesWithSingleValue.put(entryId, item.getStr().getValue());
435 }
436 }
437 }
438 }
439 }
440 }
Rico Windf9c505d2023-08-09 15:41:46 +0200441 }
442}