1 /*
2  * Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package com.sun.tools.sjavac;
27 
28 import java.io.File;
29 import java.io.IOException;
30 import java.nio.file.FileSystem;
31 import java.nio.file.FileVisitResult;
32 import java.nio.file.Files;
33 import java.nio.file.Path;
34 import java.nio.file.PathMatcher;
35 import java.nio.file.SimpleFileVisitor;
36 import java.nio.file.attribute.BasicFileAttributes;
37 import java.util.Set;
38 import java.util.Collections;
39 import java.util.List;
40 import java.util.ArrayList;
41 import java.util.Map;
42 import java.util.regex.PatternSyntaxException;
43 
44 /** A Source object maintains information about a source file.
45  * For example which package it belongs to and kind of source it is.
46  * The class also knows how to find source files (scanRoot) given include/exclude
47  * patterns and a root.
48  *
49  *  <p><b>This is NOT part of any supported API.
50  *  If you write code that depends on this, you do so at your own risk.
51  *  This code and its internal interfaces are subject to change or
52  *  deletion without notice.</b>
53  */
54 public class Source implements Comparable<Source> {
55     // The package the source belongs to.
56    private Package pkg;
57     // Name of this source file, relative its source root.
58     // For example: java/lang/Object.java
59     // Or if the source file is inside a module:
60     // jdk.base/java/lang/Object.java
61     private String name;
62     // What kind of file is this.
63     private String suffix;
64     // When this source file was last_modified
65     private long lastModified;
66     // The source File.
67     private File file;
68     // If the source is generated.
69     private boolean isGenerated;
70     // If the source is only linked to, not compiled.
71     private boolean linkedOnly;
72 
73     @Override
equals(Object o)74     public boolean equals(Object o) {
75         return (o instanceof Source) && name.equals(((Source)o).name);
76     }
77 
78     @Override
compareTo(Source o)79     public int compareTo(Source o) {
80         return name.compareTo(o.name);
81     }
82 
83     @Override
hashCode()84     public int hashCode() {
85         return name.hashCode();
86     }
87 
Source(Module m, String n, File f)88     public Source(Module m, String n, File f) {
89         name = n;
90         int dp = n.lastIndexOf(".");
91         if (dp != -1) {
92             suffix = n.substring(dp);
93         } else {
94             suffix = "";
95         }
96         file = f;
97         lastModified = f.lastModified();
98         linkedOnly = false;
99     }
100 
Source(Package p, String n, long lm)101     public Source(Package p, String n, long lm) {
102         pkg = p;
103         name = n;
104         int dp = n.lastIndexOf(".");
105         if (dp != -1) {
106             suffix = n.substring(dp);
107         } else {
108             suffix = "";
109         }
110         file = null;
111         lastModified = lm;
112         linkedOnly = false;
113         int ls = n.lastIndexOf('/');
114     }
115 
name()116     public String name() { return name; }
suffix()117     public String suffix() { return suffix; }
pkg()118     public Package pkg() { return pkg; }
file()119     public File   file() { return file; }
lastModified()120     public long lastModified() {
121         return lastModified;
122     }
123 
setPackage(Package p)124     public void setPackage(Package p) {
125         pkg = p;
126     }
127 
markAsGenerated()128     public void markAsGenerated() {
129         isGenerated = true;
130     }
131 
isGenerated()132     public boolean isGenerated() {
133         return isGenerated;
134     }
135 
markAsLinkedOnly()136     public void markAsLinkedOnly() {
137         linkedOnly = true;
138     }
139 
isLinkedOnly()140     public boolean isLinkedOnly() {
141         return linkedOnly;
142     }
143 
save(StringBuilder b)144     private void save(StringBuilder b) {
145         String CL = linkedOnly?"L":"C";
146         String GS = isGenerated?"G":"S";
147         b.append(GS+" "+CL+" "+name+" "+file.lastModified()+"\n");
148     }
149     // Parse a line that looks like this:
150     // S C /code/alfa/A.java 1357631228000
load(Package lastPackage, String l, boolean isGenerated)151     static public Source load(Package lastPackage, String l, boolean isGenerated) {
152         int sp = l.indexOf(' ',4);
153         if (sp == -1) return null;
154         String name = l.substring(4,sp);
155         long last_modified = Long.parseLong(l.substring(sp+1));
156 
157         boolean isLinkedOnly = false;
158         if (l.charAt(2) == 'L') {
159             isLinkedOnly = true;
160         } else if (l.charAt(2) == 'C') {
161             isLinkedOnly = false;
162         } else return null;
163 
164         Source s = new Source(lastPackage, name, last_modified);
165         s.file = new File(name);
166         if (isGenerated) s.markAsGenerated();
167         if (isLinkedOnly) s.markAsLinkedOnly();
168         return s;
169     }
170 
saveSources(Map<String,Source> sources, StringBuilder b)171     public static void saveSources(Map<String,Source> sources, StringBuilder b) {
172         List<String> sorted_sources = new ArrayList<>();
173         for (String key : sources.keySet()) {
174             sorted_sources.add(key);
175         }
176         Collections.sort(sorted_sources);
177         for (String key : sorted_sources) {
178             Source s = sources.get(key);
179             s.save(b);
180         }
181     }
182 
183     /**
184      * Recurse into the directory root and find all files matching the excl/incl/exclfiles/inclfiles rules.
185      * Detects the existence of module-info.java files and presumes that the directory it resides in
186      * is the name of the current module.
187      */
scanRoot(File root, Set<String> suffixes, List<String> excludes, List<String> includes, Map<String,Source> foundFiles, Map<String,Module> foundModules, final Module currentModule, boolean permitSourcesWithoutPackage, boolean inGensrc, boolean inLinksrc)188     static public void scanRoot(File root,
189                                 Set<String> suffixes,
190                                 List<String> excludes,
191                                 List<String> includes,
192                                 Map<String,Source> foundFiles,
193                                 Map<String,Module> foundModules,
194                                 final Module currentModule,
195                                 boolean permitSourcesWithoutPackage,
196                                 boolean inGensrc,
197                                 boolean inLinksrc)
198                                         throws IOException, ProblemException {
199 
200         if (root == null)
201             return;
202 
203         FileSystem fs = root.toPath().getFileSystem();
204 
205         if (includes.isEmpty()) {
206             includes = Collections.singletonList("**");
207         }
208 
209         List<PathMatcher> includeMatchers = createPathMatchers(fs, includes);
210         List<PathMatcher> excludeMatchers = createPathMatchers(fs, excludes);
211 
212         Files.walkFileTree(root.toPath(), new SimpleFileVisitor<Path>() {
213             @Override
214             public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
215 
216                 Path relToRoot = root.toPath().relativize(file);
217 
218                 if (includeMatchers.stream().anyMatch(im -> im.matches(relToRoot))
219                         && excludeMatchers.stream().noneMatch(em -> em.matches(relToRoot))
220                         && suffixes.contains(Util.fileSuffix(file))) {
221 
222                     // TODO: Test this.
223                     Source existing = foundFiles.get(file);
224                     if (existing != null) {
225                         throw new IOException("You have already added the file "+file+" from "+existing.file().getPath());
226                     }
227                     existing = currentModule.lookupSource(file.toString());
228                     if (existing != null) {
229 
230                             // Oops, the source is already added, could be ok, could be not, let's check.
231                             if (inLinksrc) {
232                                 // So we are collecting sources for linking only.
233                                 if (existing.isLinkedOnly()) {
234                                     // Ouch, this one is also for linking only. Bad.
235                                     throw new IOException("You have already added the link only file " + file + " from " + existing.file().getPath());
236                                 }
237                                 // Ok, the existing source is to be compiled. Thus this link only is redundant
238                                 // since all compiled are also linked to. Continue to the next source.
239                                 // But we need to add the source, so that it will be visible to linking,
240                                 // if not the multi core compile will fail because a JavaCompiler cannot
241                                 // find the necessary dependencies for its part of the source.
242                                 foundFiles.put(file.toString(), existing);
243                             } else {
244                                 // We are looking for sources to compile, if we find an existing to be compiled
245                                 // source with the same name, it is an internal error, since we must
246                                 // find the sources to be compiled before we find the sources to be linked to.
247                                 throw new IOException("Internal error: Double add of file " + file + " from " + existing.file().getPath());
248                             }
249 
250                     } else {
251 
252                         //////////////////////////////////////////////////////////////
253                         // Add source
254                         Source s = new Source(currentModule, file.toString(), file.toFile());
255                         if (inGensrc) {
256                             s.markAsGenerated();
257                         }
258                         if (inLinksrc) {
259                             s.markAsLinkedOnly();
260                         }
261                         String pkg = packageOfJavaFile(root.toPath(), file);
262                         pkg = currentModule.name() + ":" + pkg;
263                         foundFiles.put(file.toString(), s);
264                         currentModule.addSource(pkg, s);
265                         //////////////////////////////////////////////////////////////
266                     }
267                 }
268 
269                 return FileVisitResult.CONTINUE;
270             }
271         });
272     }
273 
createPathMatchers(FileSystem fs, List<String> patterns)274     private static List<PathMatcher> createPathMatchers(FileSystem fs, List<String> patterns) {
275         List<PathMatcher> matchers = new ArrayList<>();
276         for (String pattern : patterns) {
277             try {
278                 matchers.add(fs.getPathMatcher("glob:" + pattern));
279             } catch (PatternSyntaxException e) {
280                 Log.error("Invalid pattern: " + pattern);
281                 throw e;
282             }
283         }
284         return matchers;
285     }
286 
packageOfJavaFile(Path sourceRoot, Path javaFile)287     private static String packageOfJavaFile(Path sourceRoot, Path javaFile) {
288         Path javaFileDir = javaFile.getParent();
289         Path packageDir = sourceRoot.relativize(javaFileDir);
290         List<String> separateDirs = new ArrayList<>();
291         for (Path pathElement : packageDir) {
292             separateDirs.add(pathElement.getFileName().toString());
293         }
294         return String.join(".", separateDirs);
295     }
296 
297     @Override
toString()298     public String toString() {
299         return String.format("%s[pkg: %s, name: %s, suffix: %s, file: %s, isGenerated: %b, linkedOnly: %b]",
300                              getClass().getSimpleName(),
301                              pkg,
302                              name,
303                              suffix,
304                              file,
305                              isGenerated,
306                              linkedOnly);
307     }
308 }
309