1 /*
2  * Copyright (C) 2017 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 package libcore;
18 
19 import java.io.File;
20 import java.io.IOException;
21 import java.nio.file.Path;
22 import java.nio.file.Paths;
23 import java.util.ArrayList;
24 import java.util.Arrays;
25 import java.util.Collections;
26 import java.util.HashMap;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.Objects;
30 import java.util.Set;
31 import java.util.TreeSet;
32 import java.util.regex.Matcher;
33 import java.util.regex.Pattern;
34 
35 /**
36  * A set of .java files (either from ojluni or from an upstream).
37  */
38 abstract class Repository {
39 
40     /**
41      * Maps from a file's (current) relPath to the corresponding OpenJDK relPath from
42      * which it has been, and still remains, renamed.
43      */
44     static final Map<Path, Path> OPENJDK_REL_PATH = historicRenames();
45 
historicRenames()46     static Map<Path, Path> historicRenames() {
47         Map<Path, Path> result = new HashMap<>();
48         // renamed in libcore commit 583eb0e4738456f0547014a4857a14456be267ee
49         result.put(Paths.get("native/linux_close.cpp"), Paths.get("native/linux_close.c"));
50         // Map ByteBufferAs*Buffer.java to an upstream file, even though there is
51         // not a 1:1 correspondence. This isn't perfect, but allows some rough
52         // comparison. See http://b/111583940
53         //
54         // More detail:
55         // The RI has four different generated files ...Buffer{B,L,RB,RL}.java
56         // for each of these six files specializing on big endian, little endian,
57         // read-only big endian, and read-only little endian, respectively. Those
58         // 6 x 4 files are generated from a single template:
59         //     java/nio/ByteBufferAs-X-Buffer.java.template
60         //
61         // On Android, the four variants {B,L,RB,RL} for each of the six types
62         // are folded into a single class with behavior configured via additional
63         // constructor arguments.
64         //
65         // For now, we map to upstream's "B" variant; "B" is more similar to
66         // Android's files than "RB" or "RL"; the choice of "B" vs. "L" is arbitrary.
67         for (String s : Arrays.asList("Char", "Double", "Float", "Int", "Long", "Short")) {
68             Path ojluniPath = Paths.get("java/nio/ByteBufferAs" + s + "Buffer.java");
69             Path upstreamPath =
70                     Paths.get("java/nio/ByteBufferAs" + s + "BufferB.java");
71             result.put(ojluniPath, upstreamPath);
72         }
73         return Collections.unmodifiableMap(result);
74     }
75 
76     protected final Path rootPath;
77     protected final String name;
78     protected final List<String> sourceDirs;
79 
Repository(Path rootPath, String name, List<String> sourceDirs)80     protected Repository(Path rootPath, String name, List<String> sourceDirs) {
81         this.rootPath = Objects.requireNonNull(rootPath);
82         this.name = Objects.requireNonNull(name);
83         this.sourceDirs = Objects.requireNonNull(sourceDirs);
84         if (!rootPath.toFile().isDirectory()) {
85             throw new IllegalArgumentException("Missing or not a directory: " + rootPath);
86         }
87     }
88 
89     /**
90      * @param relPath a relative path of a .java file in the repository, e.g.
91      *        "java/util/ArrayList.java".
92      * @return the path of the indicated file (either absolute, or relative to the current
93      *         working directory), or null if the file does not exist in this Repository.
94      */
absolutePath(Path relPath)95     public final Path absolutePath(Path relPath) {
96         Path p = pathFromRepository(relPath);
97         return p == null ? null : rootPath.resolve(p).toAbsolutePath();
98     }
99 
pathFromRepository(Path relPath)100     public Path pathFromRepository(Path relPath) {
101         // Search across all sourceDirs for the indicated file.
102         for (String sourceDir : sourceDirs) {
103             Path repositoryRelativePath = Paths.get(sourceDir).resolve(relPath);
104             File file = rootPath.resolve(repositoryRelativePath).toFile();
105             if (file.exists()) {
106                 return repositoryRelativePath;
107             }
108         }
109         return null;
110     }
111 
rootPath()112     public final Path rootPath() {
113         return rootPath;
114     }
115 
116     @Override
hashCode()117     public int hashCode() {
118       return rootPath.hashCode();
119     }
120 
121     @Override
equals(Object obj)122     public boolean equals(Object obj) {
123       return (obj instanceof Repository) && rootPath.equals(((Repository) obj).rootPath);
124     }
125 
126     /**
127      * @return A human readable name to identify this repository, suitable for use as a
128      *         directory name.
129      */
name()130     public final String name() {
131         return name;
132     }
133 
134     @Override
toString()135     public String toString() {
136         return name() + " repository";
137     }
138 
139     /**
140      * A checkout of the hg repository of OpenJDK 9 or higher, located in the
141      * subdirectory {@code upstreamName} under the directory {@code upstreamRoot}.
142      */
openJdk9(Path upstreamRoot, String upstreamName)143     public static Repository openJdk9(Path upstreamRoot, String upstreamName) {
144         List<String> sourceDirs = Arrays.asList(
145             "jdk/src/java.base/share/classes",
146             "jdk/src/java.logging/share/classes",
147             "jdk/src/java.prefs/share/classes",
148             "jdk/src/java.sql/share/classes",
149             "jdk/src/java.desktop/share/classes",
150             "jdk/src/java.base/solaris/classes",
151             "jdk/src/java.base/unix/classes",
152             "jdk/src/java.prefs/unix/classes",
153             "jdk/src/jdk.unsupported/share/classes",
154             "jdk/src/jdk.net/share/classes",
155             "jdk/src/java.base/linux/classes",
156             "build/linux-x86_64-normal-server-release/support/gensrc/java.base",
157 
158             // Native (.c) files
159             "jdk/src/java.base/unix/native/libjava",
160             "jdk/src/java.base/share/native/libjava",
161             "jdk/src/java.base/unix/native/libnio",
162             "jdk/src/java.base/unix/native/libnio/ch",
163             "jdk/src/java.base/unix/native/libnio/fs",
164             "jdk/src/java.base/unix/native/libnet"
165         );
166         return new OpenJdkRepository(upstreamRoot, upstreamName, sourceDirs);
167     }
168 
169     /**
170      * A checkout of the hg repository of OpenJDK 8 or earlier, located in the
171      * subdirectory {@code upstreamName} under the directory {@code upstreamRoot}.
172      */
openJdkLegacy(Path upstreamRoot, String upstreamName)173     public static Repository openJdkLegacy(Path upstreamRoot, String upstreamName) {
174         List<String> sourceDirs = new ArrayList<>();
175         sourceDirs.addAll(Arrays.asList(
176             "jdk/src/share/classes",
177             "jdk/src/solaris/classes",
178             "build/linux-x86_64-normal-server-release/jdk/gensrc"
179         ));
180 
181         // In legacy OpenJDK versions, the source files are organized into a subfolder
182         // hierarchy based on package name, whereas in Android and OpenJDK 9+ they're in
183         // a flat folder. We work around this by just searching through all of the
184         // applicable folders (from which we have sources) in legacy OpenJDK versions.
185         List<String> nativeSourceDirs = new ArrayList<>();
186         List<String> pkgPaths = Arrays.asList("", "java/io", "java/lang", "java/net", "java/nio",
187             "java/util", "java/util/zip", "sun/nio/ch", "sun/nio/fs");
188         for (String pkgPath : pkgPaths) {
189             nativeSourceDirs.add("jdk/src/solaris/native/" + pkgPath);
190             nativeSourceDirs.add("jdk/src/share/native/" + pkgPath);
191             nativeSourceDirs.add("jdk/src/solaris/native/common/" + pkgPath);
192             nativeSourceDirs.add("jdk/src/share/native/common/" + pkgPath);
193         }
194         sourceDirs.addAll(nativeSourceDirs);
195 
196         return new OpenJdkRepository(upstreamRoot, upstreamName, sourceDirs);
197     }
198 
199     /**
200      * Checkouts of hg repositories of OpenJDK 8 or earlier, located in the
201      * respective {@code upstreamNames} subdirectories under the join parent
202      * directory {@code upstreamRoot}.
203      */
openJdkLegacy(Path upstreamRoot, List<String> upstreamNames)204     public static List<Repository> openJdkLegacy(Path upstreamRoot, List<String> upstreamNames) {
205         List<Repository> result = new ArrayList<>();
206         for (String upstreamName : upstreamNames) {
207             result.add(openJdkLegacy(upstreamRoot, upstreamName));
208         }
209         return Collections.unmodifiableList(result);
210     }
211 
212     static class OjluniRepository extends Repository {
213         /**
214          * The repository of ojluni java files belonging to the Android sources under
215          * {@code buildTop}.
216          *
217          * @param buildTop The root path of an Android checkout, as identified by the
218          *        {@quote ANDROID_BUILD_TOP} environment variable.
219          */
OjluniRepository(Path buildTop)220         public OjluniRepository(Path buildTop) {
221             super(buildTop.resolve("libcore"), "ojluni",
222                 /* sourceDirs */ Arrays.asList("ojluni/src/main/java", "ojluni/src/main/native"));
223         }
224 
225 
226         @Override
pathFromRepository(Path relPath)227         public Path pathFromRepository(Path relPath) {
228             // Enforce that the file exists in ojluni
229             return Objects.requireNonNull(super.pathFromRepository(relPath));
230         }
231 
232         /**
233          * Returns the list of relative paths to files parsed from blueprint files.
234          */
loadRelPathsFromBlueprint()235         public List<Path> loadRelPathsFromBlueprint() throws IOException {
236             List<Path> result = new ArrayList<>();
237             result.addAll(loadOrderedRelPathsSetFromBlueprint(
238                 "openjdk_java_files.bp", "\"ojluni/src/main/java/(.+\\.java)\""));
239             result.addAll(loadOrderedRelPathsSetFromBlueprint(
240                 "ojluni/src/main/native/Android.bp", "\\s+\"(.+\\.(?:c|cpp))\","));
241             return result;
242         }
243 
loadOrderedRelPathsSetFromBlueprint( String blueprintPathString, String patternString)244         private Set<Path> loadOrderedRelPathsSetFromBlueprint(
245             String blueprintPathString, String patternString) throws IOException {
246             Path blueprintPath = rootPath.resolve(blueprintPathString);
247             Pattern pattern = Pattern.compile(patternString);
248             // Use TreeSet to sort and de-duplicate the result.
249             Set<Path> result = new TreeSet<>();
250             for (String line : Util.readLines(blueprintPath)) {
251                 Matcher matcher = pattern.matcher(line);
252                 while (matcher.find()) {
253                     Path relPath = Paths.get(matcher.group(1));
254                     result.add(relPath);
255                 }
256             }
257             return result;
258         }
259 
260         @Override
toString()261         public String toString() {
262             return "libcore ojluni";
263         }
264     }
265 
266     static class OpenJdkRepository extends Repository {
267 
OpenJdkRepository(Path upstreamRoot, String name, List<String> sourceDirs)268         public OpenJdkRepository(Path upstreamRoot, String name, List<String> sourceDirs) {
269             super(upstreamRoot.resolve(name), name, sourceDirs);
270         }
271 
272         @Override
pathFromRepository(Path relPath)273         public Path pathFromRepository(Path relPath) {
274             if (OPENJDK_REL_PATH.containsKey(relPath)) {
275                 relPath = OPENJDK_REL_PATH.get(relPath);
276             }
277             return super.pathFromRepository(relPath);
278         }
279 
280         @Override
toString()281         public String toString() {
282             return "OpenJDK " + name;
283         }
284     }
285 
286 }
287