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