1 /*
   2  * Copyright (c) 2012, 2017, 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.comp;
  27 
  28 import java.io.IOException;
  29 import java.io.PrintWriter;
  30 import java.net.URI;
  31 import java.nio.file.Path;
  32 import java.nio.file.Paths;
  33 import java.util.HashMap;
  34 import java.util.HashSet;
  35 import java.util.Map;
  36 import java.util.Set;
  37 
  38 import javax.tools.*;
  39 import javax.tools.JavaFileObject.Kind;
  40 
  41 import com.sun.tools.javac.file.JavacFileManager;
  42 import com.sun.tools.javac.util.DefinedBy;
  43 import com.sun.tools.javac.util.DefinedBy.Api;
  44 import com.sun.tools.javac.util.ListBuffer;
  45 
  46 /**
  47  * Intercepts reads and writes to the file system to gather
  48  * information about what artifacts are generated.
  49  *
  50  * Traps writes to certain files, if the content written is identical
  51  * to the existing file.
  52  *
  53  * Can also blind out the filemanager from seeing certain files in the file system.
  54  * Necessary to prevent javac from seeing some sources where the source path points.
  55  *
  56  *  <p><b>This is NOT part of any supported API.
  57  *  If you write code that depends on this, you do so at your own risk.
  58  *  This code and its internal interfaces are subject to change or
  59  *  deletion without notice.</b>
  60  */
  61 @com.sun.tools.javac.api.ClientCodeWrapper.Trusted
  62 public class SmartFileManager extends ForwardingJavaFileManager<JavaFileManager> {
  63 
  64     // Set of sources that can be seen by javac.
  65     Set<URI> visibleSources = new HashSet<>();
  66     // Map from modulename:packagename to artifacts.
  67     Map<String,Set<URI>> packageArtifacts = new HashMap<>();
  68 
  69     public SmartFileManager(JavaFileManager fileManager) {
  70         super(fileManager);
  71     }
  72 
  73     public void setVisibleSources(Set<URI> s) {
  74         visibleSources = s;
  75     }
  76 
  77     public void cleanArtifacts() {
  78         packageArtifacts = new HashMap<>();
  79     }
  80 
  81     /**
  82      * Set whether or not to use ct.sym as an alternate to rt.jar.
  83      */
  84     public void setSymbolFileEnabled(boolean b) {
  85         if (!(fileManager instanceof JavacFileManager))
  86             throw new IllegalStateException();
  87         ((JavacFileManager) fileManager).setSymbolFileEnabled(b);
  88     }
  89 
  90     @DefinedBy(Api.COMPILER)
  91     public String inferBinaryName(Location location, JavaFileObject file) {
  92         return super.inferBinaryName(location, locUnwrap(file));
  93     }
  94 
  95 
  96     public Map<String,Set<URI>> getPackageArtifacts() {
  97         return packageArtifacts;
  98     }
  99 
 100     @Override @DefinedBy(Api.COMPILER)
 101     public Iterable<JavaFileObject> list(Location location,
 102                                          String packageName,
 103                                          Set<Kind> kinds,
 104                                          boolean recurse) throws IOException {
 105         // TODO: Do this lazily by returning an iterable with a filtering Iterator
 106         // Acquire the list of files.
 107         Iterable<JavaFileObject> files = super.list(location, packageName, kinds, recurse);
 108         if (visibleSources.isEmpty()) {
 109             return locWrapMany(files, location);
 110         }
 111         // Now filter!
 112         ListBuffer<JavaFileObject> filteredFiles = new ListBuffer<>();
 113         for (JavaFileObject f : files) {
 114             URI uri = f.toUri();
 115             String t = uri.toString();
 116             if (t.startsWith("jar:")
 117                 || t.endsWith(".class")
 118                 || visibleSources.contains(uri)) {
 119                 filteredFiles.add(f);
 120             }
 121         }
 122 
 123         return locWrapMany(filteredFiles, location);
 124     }
 125 
 126     @Override @DefinedBy(Api.COMPILER)
 127     public JavaFileObject getJavaFileForInput(Location location,
 128                                               String className,
 129                                               Kind kind) throws IOException {
 130         JavaFileObject file = super.getJavaFileForInput(location, className, kind);
 131         file = locWrap(file, location);
 132         if (file == null || visibleSources.isEmpty()) {
 133             return file;
 134         }
 135 
 136         if (visibleSources.contains(file.toUri()) || isModuleInfo(file)) {
 137             return file;
 138         }
 139         return null;
 140     }
 141 
 142     @Override @DefinedBy(Api.COMPILER)
 143     public JavaFileObject getJavaFileForOutput(Location location,
 144                                                String className,
 145                                                Kind kind,
 146                                                FileObject sibling) throws IOException {
 147         JavaFileObject file = super.getJavaFileForOutput(location, className, kind, sibling);
 148         file = locWrap(file, location);
 149         if (file == null) return file;
 150         int dp = className.lastIndexOf('.');
 151         String pkg_name = "";
 152         if (dp != -1) {
 153             pkg_name = className.substring(0, dp);
 154         }
 155         // When modules are in use, then the mod_name might be something like "jdk_base"
 156         String mod_name = "";
 157         addArtifact(mod_name+":"+pkg_name, file.toUri());
 158         return file;
 159     }
 160 
 161     @Override @DefinedBy(Api.COMPILER)
 162     public FileObject getFileForInput(Location location,
 163                                       String packageName,
 164                                       String relativeName) throws IOException {
 165         FileObject file =  super.getFileForInput(location, packageName, relativeName);
 166         file = locWrap(file, location);
 167         if (file == null || visibleSources.isEmpty()) {
 168             return file;
 169         }
 170 
 171         if (visibleSources.contains(file.toUri()) || isModuleInfo(file)) {
 172             return file;
 173         }
 174         return null;
 175     }
 176 
 177     private boolean isModuleInfo(FileObject fo) {
 178         if (fo instanceof JavaFileObject) {
 179             JavaFileObject jfo = (JavaFileObject) fo;
 180             return jfo.isNameCompatible("module-info", Kind.SOURCE)
 181                 || jfo.isNameCompatible("module-info", Kind.CLASS);
 182         }
 183         return false;
 184     }
 185 
 186     @Override @DefinedBy(Api.COMPILER)
 187     public FileObject getFileForOutput(Location location,
 188                                        String packageName,
 189                                        String relativeName,
 190                                        FileObject sibling) throws IOException {
 191         FileObject superFile = super.getFileForOutput(location, packageName, relativeName, sibling);
 192         FileObject file = locWrap(superFile, location);
 193         if (file == null) return file;
 194 
 195         if (location.equals(StandardLocation.NATIVE_HEADER_OUTPUT) && superFile instanceof JavaFileObject) {
 196            file = new SmartFileObject((JavaFileObject) file);
 197            packageName = ":" + packageNameFromFileName(relativeName);
 198         }
 199         if (packageName.equals("")) {
 200             packageName = ":";
 201         }
 202         addArtifact(packageName, file.toUri());
 203         return file;
 204     }
 205 
 206     @Override @DefinedBy(Api.COMPILER)
 207     public Location getLocationForModule(Location location, JavaFileObject fo) throws IOException {
 208         return super.getLocationForModule(location, locUnwrap(fo));
 209     }
 210 
 211     private static String packageNameFromFileName(String fn) {
 212         StringBuilder sb = new StringBuilder();
 213         int p = fn.indexOf('_'), pp = 0;
 214         while (p != -1) {
 215             if (sb.length() > 0) sb.append('.');
 216             sb.append(fn.substring(pp,p));
 217             if (p == fn.length()-1) break;
 218             pp = p+1;
 219             p = fn.indexOf('_',pp);
 220         }
 221         return sb.toString();
 222     }
 223 
 224     void addArtifact(String pkgName, URI art) {
 225         Set<URI> s = packageArtifacts.get(pkgName);
 226         if (s == null) {
 227             s = new HashSet<>();
 228             packageArtifacts.put(pkgName, s);
 229         }
 230         s.add(art);
 231     }
 232 
 233     public static JavaFileObject locWrap(JavaFileObject jfo, Location loc) {
 234 
 235         // From sjavac's perspective platform classes are not interesting and
 236         // there is no need to track the location for these file objects.
 237         // Also, there exists some jfo instanceof checks which breaks if
 238         // the jfos for platform classes are wrapped.
 239         if (loc == StandardLocation.PLATFORM_CLASS_PATH)
 240             return jfo;
 241 
 242         return jfo == null ? null : new JavaFileObjectWithLocation<>(jfo, loc);
 243     }
 244 
 245     private static FileObject locWrap(FileObject fo, Location loc) {
 246         if (fo instanceof JavaFileObject)
 247             return locWrap((JavaFileObject) fo, loc);
 248         return fo == null ? null : new FileObjectWithLocation<>(fo, loc);
 249     }
 250 
 251     @DefinedBy(Api.COMPILER)
 252     @Override
 253     public boolean isSameFile(FileObject a, FileObject b) {
 254         return super.isSameFile(locUnwrap(a), locUnwrap(b));
 255     }
 256 
 257     private static ListBuffer<JavaFileObject> locWrapMany(Iterable<JavaFileObject> jfos,
 258                                                           Location loc) {
 259         ListBuffer<JavaFileObject> locWrapped = new ListBuffer<>();
 260         for (JavaFileObject f : jfos)
 261             locWrapped.add(locWrap(f, loc));
 262         return locWrapped;
 263     }
 264 
 265     private static FileObject locUnwrap(FileObject fo) {
 266         if (fo instanceof FileObjectWithLocation<?>)
 267             return ((FileObjectWithLocation<?>) fo).getDelegate();
 268         if (fo instanceof JavaFileObjectWithLocation<?>)
 269             return ((JavaFileObjectWithLocation<?>) fo).getDelegate();
 270         return fo;
 271     }
 272 
 273     private static JavaFileObject locUnwrap(JavaFileObject fo) {
 274         if (fo instanceof JavaFileObjectWithLocation<?>)
 275             return ((JavaFileObjectWithLocation<?>) fo).getDelegate();
 276         return fo;
 277     }
 278 }