| /* | 
 |  * Copyright (C) 2022 The Android Open Source Project | 
 |  * | 
 |  * 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.android.build.shrinker; | 
 |  | 
 | import com.android.aapt.Resources.ResourceTable; | 
 | import com.android.annotations.NonNull; | 
 | import com.android.annotations.Nullable; | 
 | import com.android.build.shrinker.obfuscation.ObfuscatedClasses; | 
 | import com.android.ide.common.resources.usage.ResourceStore; | 
 | import com.android.ide.common.resources.usage.ResourceUsageModel.Resource; | 
 | import com.android.resources.ResourceType; | 
 | import com.google.common.collect.Maps; | 
 | import com.google.common.collect.Sets; | 
 | import java.io.IOException; | 
 | import java.io.UncheckedIOException; | 
 | import java.nio.file.Files; | 
 | import java.nio.file.Path; | 
 | import java.util.Map; | 
 | import java.util.Set; | 
 | import java.util.stream.Collectors; | 
 |  | 
 | /** | 
 |  * Represents resource shrinker state that is shared between shrinker stages ResourcesGatherer, | 
 |  * ObfuscationMappingsRecorder, ResourceUsageRecorder, ResourcesGraphBuilder and each stage | 
 |  * contributes an information via changing its state. | 
 |  */ | 
 | public class ResourceShrinkerModel { | 
 |  | 
 |     private final ShrinkerDebugReporter debugReporter; | 
 |  | 
 |     private final ResourceStore resourceStore; | 
 |     private ObfuscatedClasses obfuscatedClasses = ObfuscatedClasses.NO_OBFUSCATION; | 
 |  | 
 |     private final Set<String> strings = Sets.newHashSetWithExpectedSize(300); | 
 |  | 
 |     private final Map<String, ResourceTable> resourceTableCache = Maps.newHashMap(); | 
 |  | 
 |     private boolean foundGetIdentifier = false; | 
 |     private boolean foundWebContent = false; | 
 |  | 
 |     public ResourceShrinkerModel( | 
 |             ShrinkerDebugReporter debugReporter, boolean supportMultipackages) { | 
 |         this.debugReporter = debugReporter; | 
 |         resourceStore = new ResourceStore(supportMultipackages); | 
 |     } | 
 |  | 
 |     @NonNull | 
 |     public ShrinkerDebugReporter getDebugReporter() { | 
 |         return debugReporter; | 
 |     } | 
 |  | 
 |     @NonNull | 
 |     public ResourceStore getResourceStore() { | 
 |         return resourceStore; | 
 |     } | 
 |  | 
 |     @NonNull | 
 |     public ObfuscatedClasses getObfuscatedClasses() { | 
 |         return obfuscatedClasses; | 
 |     } | 
 |  | 
 |     /** Reports recorded mappings from obfuscated classes to original classes */ | 
 |     public void setObfuscatedClasses(@NonNull ObfuscatedClasses obfuscatedClasses) { | 
 |         this.obfuscatedClasses = obfuscatedClasses; | 
 |     } | 
 |  | 
 |     /** Adds a new gathered resource to model. */ | 
 |     @NonNull | 
 |     public Resource addResource( | 
 |             @NonNull ResourceType type, | 
 |             @Nullable String packageName, | 
 |             @NonNull String name, | 
 |             @Nullable String value) { | 
 |         int intValue = -1; | 
 |         try { | 
 |             intValue = value != null ? Integer.decode(value) : -1; | 
 |         } catch (NumberFormatException e) { | 
 |             // ignore | 
 |         } | 
 |         return addResource(type, packageName, name, intValue); | 
 |     } | 
 |  | 
 |     /** Adds a new gathered resource to model. */ | 
 |     @NonNull | 
 |     public Resource addResource( | 
 |             @NonNull ResourceType type, | 
 |             @Nullable String packageName, | 
 |             @NonNull String name, | 
 |             int value) { | 
 |         return resourceStore.addResource(new Resource(packageName, type, name, value)); | 
 |     } | 
 |  | 
 |     /** Adds string constant found in code to strings pool. */ | 
 |     public void addStringConstant(@NonNull String string) { | 
 |         strings.add(string); | 
 |     } | 
 |  | 
 |     /** Returns is reference to {@code Resources#getIdentifier} is found in code. */ | 
 |     public boolean isFoundGetIdentifier() { | 
 |         return foundGetIdentifier; | 
 |     } | 
 |  | 
 |     /** Reports that reference to {@code Resources#getIdentifier} is found in code. */ | 
 |     public void setFoundGetIdentifier(boolean foundGetIdentifier) { | 
 |         this.foundGetIdentifier = foundGetIdentifier; | 
 |     } | 
 |  | 
 |     /** Returns is web content is found in code. */ | 
 |     public boolean isFoundWebContent() { | 
 |         return foundWebContent; | 
 |     } | 
 |  | 
 |     /** Reports that reference to web content is found in code. */ | 
 |     public void setFoundWebContent(boolean foundWebContent) { | 
 |         this.foundWebContent = foundWebContent; | 
 |     } | 
 |  | 
 |     /** Returns all string constants gathered from compiled classes. */ | 
 |     public Set<String> getStrings() { | 
 |         return strings; | 
 |     } | 
 |  | 
 |     /** | 
 |      * Mark resources that match string constants as reachable in case invocation of | 
 |      * {@code Resources#getIdentifier} or web content is found in code and safe mode in enabled. | 
 |      */ | 
 |     public void keepPossiblyReferencedResources() { | 
 |         if (strings.isEmpty() | 
 |                 || !resourceStore.getSafeMode() | 
 |                 || (!foundGetIdentifier && !foundWebContent)) { | 
 |             // No calls to android.content.res.Resources#getIdentifier; or user specifically asked | 
 |             // for us not to guess resources to keep | 
 |             return; | 
 |         } | 
 |  | 
 |         debugReporter.debug(() -> "" | 
 |                         + "android.content.res.Resources#getIdentifier present: " + foundGetIdentifier + "\n" | 
 |                         + "Web content present: " + foundWebContent + "\n" | 
 |                         + "Referenced Strings:\n" | 
 |                         + strings.stream() | 
 |                             .map(s -> s.trim().replace("\n", "\\n")) | 
 |                             .filter(s -> !s.isEmpty()) | 
 |                             .map(s -> s.length() > 40 ? s.substring(0, 37) + "..." : s) | 
 |                             .collect(Collectors.joining("\n")) | 
 |         ); | 
 |  | 
 |         new PossibleResourcesMarker(debugReporter, resourceStore, strings, foundWebContent) | 
 |                 .markPossibleResourcesReachable(); | 
 |     } | 
 |  | 
 |     /** | 
 |      * Reads resource table from specified path and stores it in cache to be able to reuse it if | 
 |      * the same resource table is requested by another unit. | 
 |      */ | 
 |     public ResourceTable readResourceTable(Path resourceTablePath) { | 
 |         return resourceTableCache.computeIfAbsent(resourceTablePath.toString(), (path) -> { | 
 |             try { | 
 |                 return ResourceTable.parseFrom(Files.readAllBytes(resourceTablePath)); | 
 |             } catch (IOException e) { | 
 |                 throw new UncheckedIOException(e); | 
 |             } | 
 |         }); | 
 |     } | 
 | } |