Source.java revision 2571:10fc81ac75b4
1/*
2 * Copyright (c) 2012, 2014, 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
26package com.sun.tools.sjavac;
27
28import java.io.File;
29import java.util.Set;
30import java.util.Collections;
31import java.util.List;
32import java.util.ArrayList;
33import java.util.Map;
34
35/** A Source object maintains information about a source file.
36 * For example which package it belongs to and kind of source it is.
37 * The class also knows how to find source files (scanRoot) given include/exclude
38 * patterns and a root.
39 *
40 * <p><b>This is NOT part of any supported API.
41 * If you write code that depends on this, you do so at your own
42 * risk.  This code and its internal interfaces are subject to change
43 * or deletion without notice.</b></p>
44 */
45public class Source implements Comparable<Source> {
46    // The package the source belongs to.
47   private Package pkg;
48    // Name of this source file, relative its source root.
49    // For example: java/lang/Object.java
50    // Or if the source file is inside a module:
51    // jdk.base/java/lang/Object.java
52    private String name;
53    // What kind of file is this.
54    private String suffix;
55    // When this source file was last_modified
56    private long lastModified;
57    // The source File.
58    private File file;
59    // The source root under which file resides.
60    private File root;
61    // If the source is generated.
62    private boolean isGenerated;
63    // If the source is only linked to, not compiled.
64    private boolean linkedOnly;
65
66    @Override
67    public boolean equals(Object o) {
68        return (o instanceof Source) && name.equals(((Source)o).name);
69    }
70
71    @Override
72    public int compareTo(Source o) {
73        return name.compareTo(o.name);
74    }
75
76    @Override
77    public int hashCode() {
78        return name.hashCode();
79    }
80
81    public Source(Module m, String n, File f, File r) {
82        name = n;
83        int dp = n.lastIndexOf(".");
84        if (dp != -1) {
85            suffix = n.substring(dp);
86        } else {
87            suffix = "";
88        }
89        file = f;
90        root = r;
91        lastModified = f.lastModified();
92        linkedOnly = false;
93    }
94
95    public Source(Package p, String n, long lm) {
96        pkg = p;
97        name = n;
98        int dp = n.lastIndexOf(".");
99        if (dp != -1) {
100            suffix = n.substring(dp);
101        } else {
102            suffix = "";
103        }
104        file = null;
105        root = null;
106        lastModified = lm;
107        linkedOnly = false;
108        int ls = n.lastIndexOf('/');
109    }
110
111    public String name() { return name; }
112    public String suffix() { return suffix; }
113    public Package pkg() { return pkg; }
114    public File   file() { return file; }
115    public File   root() { return root; }
116    public long lastModified() {
117        return lastModified;
118    }
119
120    public void setPackage(Package p) {
121        pkg = p;
122    }
123
124    public void markAsGenerated() {
125        isGenerated = true;
126    }
127
128    public boolean isGenerated() {
129        return isGenerated;
130    }
131
132    public void markAsLinkedOnly() {
133        linkedOnly = true;
134    }
135
136    public boolean isLinkedOnly() {
137        return linkedOnly;
138    }
139
140    private void save(StringBuilder b) {
141        String CL = linkedOnly?"L":"C";
142        String GS = isGenerated?"G":"S";
143        b.append(GS+" "+CL+" "+name+" "+file.lastModified()+"\n");
144    }
145    // Parse a line that looks like this:
146    // S C /code/alfa/A.java 1357631228000
147    static public Source load(Package lastPackage, String l, boolean isGenerated) {
148        int sp = l.indexOf(' ',4);
149        if (sp == -1) return null;
150        String name = l.substring(4,sp);
151        long last_modified = Long.parseLong(l.substring(sp+1));
152
153        boolean isLinkedOnly = false;
154        if (l.charAt(2) == 'L') {
155            isLinkedOnly = true;
156        } else if (l.charAt(2) == 'C') {
157            isLinkedOnly = false;
158        } else return null;
159
160        Source s = new Source(lastPackage, name, last_modified);
161        s.file = new File(name);
162        if (isGenerated) s.markAsGenerated();
163        if (isLinkedOnly) s.markAsLinkedOnly();
164        return s;
165    }
166
167    public static void saveSources(Map<String,Source> sources, StringBuilder b) {
168        List<String> sorted_sources = new ArrayList<>();
169        for (String key : sources.keySet()) {
170            sorted_sources.add(key);
171        }
172        Collections.sort(sorted_sources);
173        for (String key : sorted_sources) {
174            Source s = sources.get(key);
175            s.save(b);
176        }
177    }
178
179    /**
180     * Recurse into the directory root and find all files matchine the excl/incl/exclfiles/inclfiles rules.
181     * Detects the existence of module-info.java files and presumes that the directory it resides in
182     * is the name of the current module.
183     */
184    static public void scanRoot(File root,
185                                Set<String> suffixes,
186                                List<String> excludes, List<String> includes,
187                                List<String> excludeFiles, List<String> includeFiles,
188                                Map<String,Source> foundFiles,
189                                Map<String,Module> foundModules,
190                                Module currentModule,
191                                boolean permitSourcesWithoutPackage,
192                                boolean inGensrc,
193                                boolean inLinksrc)
194        throws ProblemException {
195
196        if (root == null) return;
197        int root_prefix = root.getPath().length()+1;
198        // This is the root source directory, it must not contain any Java sources files
199        // because we do not allow Java source files without a package.
200        // (Unless of course --permit-sources-without-package has been specified.)
201        // It might contain other source files however, (for -tr and -copy) these will
202        // always be included, since no package pattern can match the root directory.
203        currentModule = addFilesInDir(root, root_prefix, root, suffixes, permitSourcesWithoutPackage,
204                                       excludeFiles, includeFiles,
205                                       foundFiles, foundModules, currentModule,
206                                       inGensrc, inLinksrc);
207
208        File[] dirfiles = root.listFiles();
209        for (File d : dirfiles) {
210            if (d.isDirectory()) {
211                // Descend into the directory structure.
212                scanDirectory(d, root_prefix, root, suffixes,
213                              excludes, includes, excludeFiles, includeFiles,
214                              foundFiles, foundModules, currentModule, inGensrc, inLinksrc);
215            }
216        }
217    }
218
219    /**
220     * Test if a path matches any of the patterns given.
221     * The pattern foo/bar matches only foo/bar
222     * The pattern foo/* matches foo/bar and foo/bar/zoo etc
223     */
224    static private boolean hasMatch(String path, List<String> patterns) {
225
226        // Convert Windows '\' to '/' for the sake of comparing with the patterns
227        path = path.replace(File.separatorChar, '/');
228
229        for (String p : patterns) {
230            // Exact match
231            if (p.equals(path))
232                return true;
233
234            // Single dot the end matches this package and all its subpackages.
235            if (p.endsWith("/*")) {
236                // Remove the wildcard
237                String patprefix = p.substring(0,p.length()-2);
238                // Does the path start with the pattern prefix?
239                if (path.startsWith(patprefix)) {
240                    // If the path has the same length as the pattern prefix, then it is a match.
241                    // If the path is longer, then make sure that
242                    // the next part of the path starts with a dot (.) to prevent
243                    // wildcard matching in the middle of a package name.
244                    if (path.length()==patprefix.length() || path.charAt(patprefix.length())=='/') {
245                        return true;
246                    }
247                }
248            }
249        }
250        return false;
251    }
252
253    /**
254     * Matches patterns with the asterisk first. */
255     // The pattern foo/bar.java only matches foo/bar.java
256     // The pattern */bar.java matches foo/bar.java and zoo/bar.java etc
257    static private boolean hasFileMatch(String path, List<String> patterns) {
258        // Convert Windows '\' to '/' for the sake of comparing with the patterns
259        path = path.replace(File.separatorChar, '/');
260
261        path = Util.normalizeDriveLetter(path);
262        for (String p : patterns) {
263            // Exact match
264            if (p.equals(path)) {
265                return true;
266            }
267            // Single dot the end matches this package and all its subpackages.
268            if (p.startsWith("*")) {
269                // Remove the wildcard
270                String patsuffix = p.substring(1);
271                // Does the path start with the pattern prefix?
272                if (path.endsWith(patsuffix)) {
273                    return true;
274                }
275            }
276        }
277        return false;
278    }
279
280    /**
281     * Add the files in the directory, assuming that the file has not been excluded.
282     * Returns a fresh Module object, if this was a dir with a module-info.java file.
283     */
284    static private Module addFilesInDir(File dir, int rootPrefix, File root,
285                                        Set<String> suffixes, boolean allow_javas,
286                                        List<String> excludeFiles, List<String> includeFiles,
287                                        Map<String,Source> foundFiles,
288                                        Map<String,Module> foundModules,
289                                        Module currentModule,
290                                        boolean inGensrc,
291                                        boolean inLinksrc)
292        throws ProblemException
293    {
294        for (File f : dir.listFiles()) {
295
296            if (!f.isFile())
297                continue;
298
299            boolean should_add =
300                (excludeFiles == null || excludeFiles.isEmpty() || !hasFileMatch(f.getPath(), excludeFiles))
301                && (includeFiles == null || includeFiles.isEmpty() || hasFileMatch(f.getPath(), includeFiles));
302
303            if (!should_add)
304                continue;
305
306            if (!allow_javas && f.getName().endsWith(".java")) {
307                throw new ProblemException("No .java files are allowed in the source root "+dir.getPath()+
308                                           ", please remove "+f.getName());
309            }
310            // Extract the file name relative the root.
311            String fn = f.getPath().substring(rootPrefix);
312            // Extract the package name.
313            int sp = fn.lastIndexOf(File.separatorChar);
314            String pkg = "";
315            if (sp != -1) {
316                pkg = fn.substring(0,sp).replace('/','.');
317            }
318            // Is this a module-info.java file?
319            if (fn.endsWith("module-info.java")) {
320                // Aha! We have recursed into a module!
321                if (!currentModule.name().equals("")) {
322                    throw new ProblemException("You have an extra module-info.java inside a module! Please remove "+fn);
323                }
324                String module_name = fn.substring(0,fn.length()-16);
325                currentModule = new Module(module_name, f.getPath());
326                foundModules.put(module_name, currentModule);
327            }
328            // Extract the suffix.
329            int dp = fn.lastIndexOf(".");
330            String suffix = "";
331            if (dp > 0) {
332                suffix = fn.substring(dp);
333            }
334            // Should the file be added?
335            if (suffixes.contains(suffix)) {
336                Source of = foundFiles.get(f.getPath());
337                if (of != null) {
338                    throw new ProblemException("You have already added the file "+fn+" from "+of.file().getPath());
339                }
340                of = currentModule.lookupSource(f.getPath());
341                if (of != null) {
342                    // Oups, the source is already added, could be ok, could be not, lets check.
343                    if (inLinksrc) {
344                        // So we are collecting sources for linking only.
345                        if (of.isLinkedOnly()) {
346                            // Ouch, this one is also for linking only. Bad.
347                            throw new ProblemException("You have already added the link only file "+fn+" from "+of.file().getPath());
348                        }
349                        // Ok, the existing source is to be compiled. Thus this link only is redundant
350                        // since all compiled are also linked to. Continue to the next source.
351                        // But we need to add the source, so that it will be visible to linking,
352                        // if not the multi core compile will fail because a JavaCompiler cannot
353                        // find the necessary dependencies for its part of the source.
354                        foundFiles.put(f.getPath(), of);
355                        continue;
356                    } else {
357                        // We are looking for sources to compile, if we find an existing to be compiled
358                        // source with the same name, it is an internal error, since we must
359                        // find the sources to be compiled before we find the sources to be linked to.
360                        throw new ProblemException("Internal error: Double add of file "+fn+" from "+of.file().getPath());
361                    }
362                }
363                Source s = new Source(currentModule, f.getPath(), f, root);
364                if (inGensrc) s.markAsGenerated();
365                if (inLinksrc) {
366                    s.markAsLinkedOnly();
367                }
368                pkg = currentModule.name()+":"+pkg;
369                foundFiles.put(f.getPath(), s);
370                currentModule.addSource(pkg, s);
371            }
372        }
373        return currentModule;
374    }
375
376    private static boolean gurka = false;
377
378    static private void scanDirectory(File dir, int rootPrefix, File root,
379                                      Set<String> suffixes,
380                                      List<String> excludes, List<String> includes,
381                                      List<String> excludeFiles, List<String> includeFiles,
382                                      Map<String,Source> foundFiles,
383                                      Map<String,Module> foundModules,
384                                      Module currentModule, boolean inGensrc, boolean inLinksrc)
385        throws ProblemException {
386
387        String path = "";
388        // Remove the root prefix from the dir path
389        if (dir.getPath().length() > rootPrefix) {
390            path = dir.getPath().substring(rootPrefix);
391        }
392        // Should this package directory be included and not excluded?
393        if ((includes==null || includes.isEmpty() || hasMatch(path, includes)) &&
394            (excludes==null || excludes.isEmpty() || !hasMatch(path, excludes))) {
395            // Add the source files.
396            currentModule = addFilesInDir(dir, rootPrefix, root, suffixes, true, excludeFiles, includeFiles,
397                                          foundFiles, foundModules, currentModule, inGensrc, inLinksrc);
398        }
399
400        for (File d : dir.listFiles()) {
401            if (d.isDirectory()) {
402                // Descend into the directory structure.
403                scanDirectory(d, rootPrefix, root, suffixes,
404                              excludes, includes, excludeFiles, includeFiles,
405                              foundFiles, foundModules, currentModule, inGensrc, inLinksrc);
406            }
407        }
408    }
409}
410