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 26 package com.sun.tools.sjavac; 27 28 import java.io.File; 29 import java.util.Set; 30 import java.util.Collections; 31 import java.util.List; 32 import java.util.ArrayList; 33 import 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 risk. 42 * This code and its internal interfaces are subject to change or 43 * deletion without notice.</b> 44 */ 45 public 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(File.separatorChar,'.'); 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 }