1 /*
2  * Copyright (c) 2014, 2015, 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.javac.file;
27 
28 import java.io.IOException;
29 import java.io.UncheckedIOException;
30 import java.lang.ref.SoftReference;
31 import java.net.URI;
32 import java.nio.file.DirectoryStream;
33 import java.nio.file.FileSystem;
34 import java.nio.file.FileSystems;
35 import java.nio.file.FileSystemNotFoundException;
36 import java.nio.file.Files;
37 import java.nio.file.Path;
38 import java.nio.file.ProviderNotFoundException;
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;
getSharedInstance()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. */
instance(Context context)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 
isAvailable()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      * A lazily evaluated set of entries about the contents of the jrt: file system.
99      */
100     private final Map<RelativeDirectory, SoftReference<Entry>> entries;
101 
102     /**
103      * An entry provides cached info about a specific package directory within jrt:.
104      */
105     class Entry {
106         /**
107          * The regular files for this package.
108          * For now, assume just one instance of each file across all modules.
109          */
110         final Map<String, Path> files;
111 
112         /**
113          * The set of subdirectories in jrt: for this package.
114          */
115         final Set<RelativeDirectory> subdirs;
116 
117         /**
118          * The info that used to be in ct.sym for classes in this package.
119          */
120         final CtSym ctSym;
121 
Entry(Map<String, Path> files, Set<RelativeDirectory> subdirs, CtSym ctSym)122         private Entry(Map<String, Path> files, Set<RelativeDirectory> subdirs, CtSym ctSym) {
123             this.files = files;
124             this.subdirs = subdirs;
125             this.ctSym = ctSym;
126         }
127     }
128 
129     /**
130      * The info that used to be in ct.sym for classes in a package.
131      */
132     public static class CtSym {
133         /**
134          * The classes in this package are internal and not visible.
135          */
136         public final boolean hidden;
137         /**
138          * The classes in this package are proprietary and will generate a warning.
139          */
140         public final boolean proprietary;
141         /**
142          * The minimum profile in which classes in this package are available.
143          */
144         public final String minProfile;
145 
CtSym(boolean hidden, boolean proprietary, String minProfile)146         CtSym(boolean hidden, boolean proprietary, String minProfile) {
147             this.hidden = hidden;
148             this.proprietary = proprietary;
149             this.minProfile = minProfile;
150         }
151 
152         @Override
toString()153         public String toString() {
154             StringBuilder sb = new StringBuilder("CtSym[");
155             boolean needSep = false;
156             if (hidden) {
157                 sb.append("hidden");
158                 needSep = true;
159             }
160             if (proprietary) {
161                 if (needSep) sb.append(",");
162                 sb.append("proprietary");
163                 needSep = true;
164             }
165             if (minProfile != null) {
166                 if (needSep) sb.append(",");
167                 sb.append(minProfile);
168             }
169             sb.append("]");
170             return sb.toString();
171         }
172 
173         static final CtSym EMPTY = new CtSym(false, false, null);
174     }
175 
176     /**
177      * Create and initialize the index.
178      */
JRTIndex()179     private JRTIndex() throws IOException {
180         jrtfs = FileSystems.getFileSystem(URI.create("jrt:/"));
181         entries = new HashMap<>();
182     }
183 
getCtSym(CharSequence packageName)184     public CtSym getCtSym(CharSequence packageName) throws IOException {
185         return getEntry(RelativeDirectory.forPackage(packageName)).ctSym;
186     }
187 
getEntry(RelativeDirectory rd)188     synchronized Entry getEntry(RelativeDirectory rd) throws IOException {
189         SoftReference<Entry> ref = entries.get(rd);
190         Entry e = (ref == null) ? null : ref.get();
191         if (e == null) {
192             Map<String, Path> files = new LinkedHashMap<>();
193             Set<RelativeDirectory> subdirs = new LinkedHashSet<>();
194             Path dir;
195             if (rd.path.isEmpty()) {
196                 dir = jrtfs.getPath("/modules");
197             } else {
198                 Path pkgs = jrtfs.getPath("/packages");
199                 dir = pkgs.resolve(rd.getPath().replaceAll("/$", "").replace("/", "."));
200             }
201             if (Files.exists(dir)) {
202                 try (DirectoryStream<Path> modules = Files.newDirectoryStream(dir)) {
203                     for (Path module: modules) {
204                         if (Files.isSymbolicLink(module))
205                             module = Files.readSymbolicLink(module);
206                         Path p = rd.resolveAgainst(module);
207                         if (!Files.exists(p))
208                             continue;
209                         try (DirectoryStream<Path> stream = Files.newDirectoryStream(p)) {
210                             for (Path entry: stream) {
211                                 String name = entry.getFileName().toString();
212                                 if (Files.isRegularFile(entry)) {
213                                     // TODO: consider issue of files with same name in different modules
214                                     files.put(name, entry);
215                                 } else if (Files.isDirectory(entry)) {
216                                     subdirs.add(new RelativeDirectory(rd, name));
217                                 }
218                             }
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 
isInJRT(FileObject fo)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 
getCtInfo(RelativeDirectory dir)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 }
275