1 /*
   2  * Copyright (c) 2012, 2016, 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
  74     public boolean equals(Object o) {
  75         return (o instanceof Source) && name.equals(((Source)o).name);
  76     }
  77 
  78     @Override
  79     public int compareTo(Source o) {
  80         return name.compareTo(o.name);
  81     }
  82 
  83     @Override
  84     public int hashCode() {
  85         return name.hashCode();
  86     }
  87 
  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 
 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 
 116     public String name() { return name; }
 117     public String suffix() { return suffix; }
 118     public Package pkg() { return pkg; }
 119     public File   file() { return file; }
 120     public long lastModified() {
 121         return lastModified;
 122     }
 123 
 124     public void setPackage(Package p) {
 125         pkg = p;
 126     }
 127 
 128     public void markAsGenerated() {
 129         isGenerated = true;
 130     }
 131 
 132     public boolean isGenerated() {
 133         return isGenerated;
 134     }
 135 
 136     public void markAsLinkedOnly() {
 137         linkedOnly = true;
 138     }
 139 
 140     public boolean isLinkedOnly() {
 141         return linkedOnly;
 142     }
 143 
 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
 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 
 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      */
 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, lets 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 
 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 
 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
 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 }