1 /*
   2  * Copyright (c) 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 package com.sun.tools.javac.file;
  26 
  27 import java.io.IOException;
  28 import java.io.UncheckedIOException;
  29 import java.lang.ref.SoftReference;
  30 import java.net.URI;
  31 import java.nio.file.DirectoryStream;
  32 import java.nio.file.FileSystem;
  33 import java.nio.file.FileSystemNotFoundException;
  34 import java.nio.file.FileSystems;
  35 import java.nio.file.Files;
  36 import java.nio.file.Path;
  37 import java.nio.file.ProviderNotFoundException;
  38 import java.util.Collections;
  39 import java.util.HashMap;
  40 import java.util.LinkedHashMap;
  41 import java.util.LinkedHashSet;
  42 import java.util.Map;
  43 import java.util.MissingResourceException;
  44 import java.util.ResourceBundle;
  45 import java.util.Set;
  46 
  47 import javax.tools.FileObject;
  48 
  49 import com.sun.tools.javac.file.RelativePath.RelativeDirectory;
  50 import com.sun.tools.javac.nio.PathFileObject;
  51 import com.sun.tools.javac.util.Context;
  52 import com.sun.tools.sjavac.comp.JavaFileObjectWithLocation;
  53 
  54 /**
  55  * A package-oriented index into the jrt: filesystem.
  56  */
  57 public class JRTIndex {
  58     /** Get a shared instance of the cache. */
  59     private static JRTIndex sharedInstance;
  60     public synchronized static JRTIndex getSharedInstance() {
  61         if (sharedInstance == null) {
  62             try {
  63                 sharedInstance = new JRTIndex();
  64             } catch (IOException e) {
  65                 throw new UncheckedIOException(e);
  66             }
  67         }
  68         return sharedInstance;
  69     }
  70 
  71     /** Get a context-specific instance of a cache. */
  72     public static JRTIndex instance(Context context) {
  73         try {
  74             JRTIndex instance = context.get(JRTIndex.class);
  75             if (instance == null)
  76                 context.put(JRTIndex.class, instance = new JRTIndex());
  77             return instance;
  78         } catch (IOException e) {
  79             throw new UncheckedIOException(e);
  80         }
  81     }
  82 
  83     public static boolean isAvailable() {
  84         try {
  85             FileSystems.getFileSystem(URI.create("jrt:/"));
  86             return true;
  87         } catch (ProviderNotFoundException | FileSystemNotFoundException e) {
  88             return false;
  89         }
  90     }
  91 
  92 
  93     /**
  94      * The jrt: file system.
  95      */
  96     private final FileSystem jrtfs;
  97 
  98     /**
  99      * The set of module directories within the jrt: file system.
 100      */
 101     private final Set<Path> jrtModules;
 102 
 103     /**
 104      * A lazily evaluated set of entries about the contents of the jrt: file system.
 105      */
 106     private final Map<RelativeDirectory, SoftReference<Entry>> entries;
 107 
 108     /**
 109      * An entry provides cached info about a specific package directory within jrt:.
 110      */
 111     class Entry {
 112         /**
 113          * The regular files for this package.
 114          * For now, assume just one instance of each file across all modules.
 115          */
 116         final Map<String, Path> files;
 117 
 118         /**
 119          * The set of subdirectories in jrt: for this package.
 120          */
 121         final Set<RelativeDirectory> subdirs;
 122 
 123         /**
 124          * The info that used to be in ct.sym for classes in this package.
 125          */
 126         final CtSym ctSym;
 127 
 128         private Entry(Map<String, Path> files, Set<RelativeDirectory> subdirs, CtSym ctSym) {
 129             this.files = files;
 130             this.subdirs = subdirs;
 131             this.ctSym = ctSym;
 132         }
 133     }
 134 
 135     /**
 136      * The info that used to be in ct.sym for classes in a package.
 137      */
 138     public static class CtSym {
 139         /**
 140          * The classes in this package are internal and not visible.
 141          */
 142         public final boolean hidden;
 143         /**
 144          * The classes in this package are proprietary and will generate a warning.
 145          */
 146         public final boolean proprietary;
 147         /**
 148          * The minimum profile in which classes in this package are available.
 149          */
 150         public final String minProfile;
 151 
 152         CtSym(boolean hidden, boolean proprietary, String minProfile) {
 153             this.hidden = hidden;
 154             this.proprietary = proprietary;
 155             this.minProfile = minProfile;
 156         }
 157 
 158         @Override
 159         public String toString() {
 160             StringBuilder sb = new StringBuilder("CtSym[");
 161             boolean needSep = false;
 162             if (hidden) {
 163                 sb.append("hidden");
 164                 needSep = true;
 165             }
 166             if (proprietary) {
 167                 if (needSep) sb.append(",");
 168                 sb.append("proprietary");
 169                 needSep = true;
 170             }
 171             if (minProfile != null) {
 172                 if (needSep) sb.append(",");
 173                 sb.append(minProfile);
 174             }
 175             sb.append("]");
 176             return sb.toString();
 177         }
 178 
 179         static final CtSym EMPTY = new CtSym(false, false, null);
 180     }
 181 
 182     /**
 183      * Create and initialize the index.
 184      */
 185     private JRTIndex() throws IOException {
 186         jrtfs = FileSystems.getFileSystem(URI.create("jrt:/"));
 187         jrtModules = new LinkedHashSet<>();
 188         Path root = jrtfs.getPath("/");
 189         try (DirectoryStream<Path> stream = Files.newDirectoryStream(root)) {
 190             for (Path entry: stream) {
 191                 if (Files.isDirectory(entry))
 192                     jrtModules.add(entry);
 193             }
 194         }
 195         entries = new HashMap<>();
 196     }
 197 
 198     public CtSym getCtSym(CharSequence packageName) throws IOException {
 199         return getEntry(RelativeDirectory.forPackage(packageName)).ctSym;
 200     }
 201 
 202     synchronized Entry getEntry(RelativeDirectory rd) throws IOException {
 203         SoftReference<Entry> ref = entries.get(rd);
 204         Entry e = (ref == null) ? null : ref.get();
 205         if (e == null) {
 206             Map<String, Path> files = new LinkedHashMap<>();
 207             Set<RelativeDirectory> subdirs = new LinkedHashSet<>();
 208             for (Path module: jrtModules) {
 209                 Path p = rd.getFile(module);
 210                 if (!Files.exists(p))
 211                     continue;
 212                 try (DirectoryStream<Path> stream = Files.newDirectoryStream(p)) {
 213                     for (Path entry: stream) {
 214                         String name = entry.getFileName().toString();
 215                         if (Files.isRegularFile(entry)) {
 216                             // TODO: consider issue of files with same name in different modules
 217                             files.put(name, entry);
 218                         } else if (Files.isDirectory(entry)) {
 219                             subdirs.add(new RelativeDirectory(rd, name));
 220                         }
 221                     }
 222                 }
 223             }
 224             e = new Entry(Collections.unmodifiableMap(files),
 225                     Collections.unmodifiableSet(subdirs),
 226                     getCtInfo(rd));
 227             entries.put(rd, new SoftReference<>(e));
 228         }
 229         return e;
 230     }
 231 
 232     public boolean isInJRT(FileObject fo) {
 233         if (fo instanceof JavaFileObjectWithLocation) {
 234             fo = ((JavaFileObjectWithLocation<?>) fo).getDelegate();
 235         }
 236         if (fo instanceof PathFileObject) {
 237             Path path = ((PathFileObject) fo).getPath();
 238             return (path.getFileSystem() == jrtfs);
 239         } else {
 240             return false;
 241         }
 242     }
 243 
 244     private CtSym getCtInfo(RelativeDirectory dir) {
 245         if (dir.path.isEmpty())
 246             return CtSym.EMPTY;
 247         // It's a side-effect of the default build rules that ct.properties
 248         // ends up as a resource bundle.
 249         if (ctBundle == null) {
 250             final String bundleName = "com.sun.tools.javac.resources.ct";
 251             ctBundle = ResourceBundle.getBundle(bundleName);
 252         }
 253         try {
 254             String attrs = ctBundle.getString(dir.path.replace('/', '.') + '*');
 255             boolean hidden = false;
 256             boolean proprietary = false;
 257             String minProfile = null;
 258             for (String attr: attrs.split(" +", 0)) {
 259                 switch (attr) {
 260                     case "hidden":
 261                         hidden = true;
 262                         break;
 263                     case "proprietary":
 264                         proprietary = true;
 265                         break;
 266                     default:
 267                         minProfile = attr;
 268                 }
 269             }
 270             return new CtSym(hidden, proprietary, minProfile);
 271         } catch (MissingResourceException e) {
 272             return CtSym.EMPTY;
 273         }
 274 
 275     }
 276 
 277     private ResourceBundle ctBundle;
 278 }