• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2025 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 package com.android.dependencymapper;
17 
18 import com.android.dependencymapper.DependencyProto;
19 
20 import java.util.ArrayList;
21 import java.util.HashMap;
22 import java.util.HashSet;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.Set;
26 
27 /**
28  * This class binds {@link List<ClassDependencyData>} and {@link List<JavaSourceData>} together as a
29  * flat map, which represents dependency related attributes of a java file.
30  */
31 public class DependencyMapper {
32     private final List<ClassDependencyData> mClassAnalysisList;
33     private final List<JavaSourceData> mJavaSourceDataList;
34     private final Map<String, String> mClassToSourceMap = new HashMap<>();
35     private final Map<String, Set<String>> mFileDependencies = new HashMap<>();
36     private final Set<String> mDependencyToAll = new HashSet<>();
37     private final Map<String, Set<String>> mSourceToClasses = new HashMap<>();
38 
DependencyMapper(List<ClassDependencyData> classAnalysisList, List<JavaSourceData> javaSourceDataList)39     public DependencyMapper(List<ClassDependencyData> classAnalysisList, List<JavaSourceData> javaSourceDataList) {
40         this.mClassAnalysisList = classAnalysisList;
41         this.mJavaSourceDataList = javaSourceDataList;
42     }
43 
buildDependencyMaps()44     public DependencyProto.FileDependencyList buildDependencyMaps() {
45         buildClassDependencyMaps();
46         buildSourceToClassMap();
47         return createFileDependencies();
48     }
49 
buildClassDependencyMaps()50     private void buildClassDependencyMaps() {
51         // Create a map between package appended file names and file paths.
52         Map<String, String> sourcePaths = generateSourcePaths();
53         // A map between qualified className and its dependencies
54         Map<String, Set<String>> classDependencies = new HashMap<>();
55         // A map between constant values and the their declarations.
56         Map<Object, Set<String>> constantRegistry = new HashMap<>();
57         // A map between constant values and the their inlined usages.
58         Map<Object, Set<String>> inlinedUsages = new HashMap<>();
59 
60         for (ClassDependencyData analysis : mClassAnalysisList) {
61             String className = analysis.getQualifiedName();
62 
63             // Compute qualified class name to source path map.
64             String sourceKey = analysis.getPackagePrependedClassSource();
65             String sourcePath = sourcePaths.get(sourceKey);
66             mClassToSourceMap.put(className, sourcePath);
67 
68             // compute classDependencies
69             classDependencies.computeIfAbsent(className, k ->
70                     new HashSet<>()).addAll(analysis.getClassDependencies());
71 
72             // Compute constantRegistry
73             analysis.getConstantsDefined().forEach(c ->
74                     constantRegistry.computeIfAbsent(c, k -> new HashSet<>()).add(className));
75             // Compute inlinedUsages map.
76             analysis.inlinedUsages().forEach(u ->
77                     inlinedUsages.computeIfAbsent(u, k -> new HashSet<>()).add(className));
78 
79             if (analysis.isDependencyToAll()) {
80                 mDependencyToAll.add(sourcePath);
81             }
82         }
83         // Finally build file dependencies
84         buildFileDependencies(
85                 combineDependencies(classDependencies, inlinedUsages, constantRegistry));
86     }
87 
generateSourcePaths()88     private Map<String, String> generateSourcePaths() {
89         Map<String, String> sourcePaths = new HashMap<>();
90         mJavaSourceDataList.forEach(data ->
91                 sourcePaths.put(data.getPackagePrependedFileName(), data.getFilePath()));
92         return sourcePaths;
93     }
94 
combineDependencies(Map<String, Set<String>> classDependencies, Map<Object, Set<String>> inlinedUsages, Map<Object, Set<String>> constantRegistry)95     private Map<String, Set<String>> combineDependencies(Map<String, Set<String>> classDependencies,
96             Map<Object, Set<String>> inlinedUsages,
97             Map<Object, Set<String>> constantRegistry) {
98         Map<String, Set<String>> combined = new HashMap<>(
99                 buildConstantDependencies(inlinedUsages, constantRegistry));
100         classDependencies.forEach((k, v) ->
101                 combined.computeIfAbsent(k, key -> new HashSet<>()).addAll(v));
102         return combined;
103     }
104 
buildConstantDependencies( Map<Object, Set<String>> inlinedUsages, Map<Object, Set<String>> constantRegistry)105     private Map<String, Set<String>> buildConstantDependencies(
106             Map<Object, Set<String>> inlinedUsages, Map<Object, Set<String>> constantRegistry) {
107         Map<String, Set<String>> constantDependencies = new HashMap<>();
108         for (Map.Entry<Object, Set<String>> usageEntry : inlinedUsages.entrySet()) {
109             Object usage = usageEntry.getKey();
110             Set<String> usageClasses = usageEntry.getValue();
111             if (constantRegistry.containsKey(usage)) {
112                 Set<String> declarationClasses = constantRegistry.get(usage);
113                 for (String usageClass : usageClasses) {
114                     // Sometimes Usage and Declarations are in the same file, we remove such cases
115                     // to prevent circular dependency.
116                     declarationClasses.remove(usageClass);
117                     constantDependencies.computeIfAbsent(usageClass, k ->
118                             new HashSet<>()).addAll(declarationClasses);
119                 }
120             }
121         }
122 
123         return constantDependencies;
124     }
125 
buildFileDependencies(Map<String, Set<String>> combinedClassDependencies)126     private void buildFileDependencies(Map<String, Set<String>> combinedClassDependencies) {
127         combinedClassDependencies.forEach((className, dependencies) -> {
128             String sourceFile = mClassToSourceMap.get(className);
129             if (sourceFile == null) {
130                 throw new IllegalArgumentException("Class '" + className
131                         + "' does not have a corresponding source file.");
132             }
133             mFileDependencies.computeIfAbsent(sourceFile, k -> new HashSet<>());
134             dependencies.forEach(dependency -> {
135                 String dependencySource = mClassToSourceMap.get(dependency);
136                 if (dependencySource == null) {
137                     throw new IllegalArgumentException("Dependency '" + dependency
138                             + "' does not have a corresponding source file.");
139                 }
140                 mFileDependencies.get(sourceFile).add(dependencySource);
141             });
142         });
143     }
144 
buildSourceToClassMap()145     private void buildSourceToClassMap() {
146         mClassToSourceMap.forEach((className, sourceFile) ->
147                 mSourceToClasses.computeIfAbsent(sourceFile, k ->
148                         new HashSet<>()).add(className));
149     }
150 
createFileDependencies()151     private DependencyProto.FileDependencyList createFileDependencies() {
152         List<DependencyProto.FileDependency> fileDependencies = new ArrayList<>();
153         mFileDependencies.forEach((file, dependencies) -> {
154             DependencyProto.FileDependency dependency = DependencyProto.FileDependency.newBuilder()
155                     .setFilePath(file)
156                     .setIsDependencyToAll(mDependencyToAll.contains(file))
157                     .addAllGeneratedClasses(mSourceToClasses.get(file))
158                     .addAllFileDependencies(dependencies)
159                     .build();
160             fileDependencies.add(dependency);
161         });
162         return DependencyProto.FileDependencyList.newBuilder()
163                 .addAllFileDependency(fileDependencies).build();
164     }
165 }
166