JRTIndex.java revision 2741:eb5fc32790eb
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 */
25package com.sun.tools.javac.file;
26
27import java.io.IOException;
28import java.io.UncheckedIOException;
29import java.lang.ref.SoftReference;
30import java.net.URI;
31import java.nio.file.DirectoryStream;
32import java.nio.file.FileSystem;
33import java.nio.file.FileSystems;
34import java.nio.file.FileSystemNotFoundException;
35import java.nio.file.Files;
36import java.nio.file.Path;
37import java.nio.file.ProviderNotFoundException;
38import java.nio.file.spi.FileSystemProvider;
39import java.util.Collections;
40import java.util.HashMap;
41import java.util.LinkedHashMap;
42import java.util.LinkedHashSet;
43import java.util.Map;
44import java.util.MissingResourceException;
45import java.util.ResourceBundle;
46import java.util.Set;
47
48import javax.tools.FileObject;
49
50import com.sun.tools.javac.file.RelativePath.RelativeDirectory;
51import com.sun.tools.javac.nio.PathFileObject;
52import com.sun.tools.javac.util.Context;
53
54/**
55 * A package-oriented index into the jrt: filesystem.
56 */
57public 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 PathFileObject) {
234            Path path = ((PathFileObject) fo).getPath();
235            return (path.getFileSystem() == jrtfs);
236        } else {
237            return false;
238        }
239    }
240
241    private CtSym getCtInfo(RelativeDirectory dir) {
242        if (dir.path.isEmpty())
243            return CtSym.EMPTY;
244        // It's a side-effect of the default build rules that ct.properties
245        // ends up as a resource bundle.
246        if (ctBundle == null) {
247            final String bundleName = "com.sun.tools.javac.resources.ct";
248            ctBundle = ResourceBundle.getBundle(bundleName);
249        }
250        try {
251            String attrs = ctBundle.getString(dir.path.replace('/', '.') + '*');
252            boolean hidden = false;
253            boolean proprietary = false;
254            String minProfile = null;
255            for (String attr: attrs.split(" +", 0)) {
256                switch (attr) {
257                    case "hidden":
258                        hidden = true;
259                        break;
260                    case "proprietary":
261                        proprietary = true;
262                        break;
263                    default:
264                        minProfile = attr;
265                }
266            }
267            return new CtSym(hidden, proprietary, minProfile);
268        } catch (MissingResourceException e) {
269            return CtSym.EMPTY;
270        }
271
272    }
273
274    private ResourceBundle ctBundle;
275}
276