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
26package com.sun.tools.javac.file;
27
28import java.io.IOException;
29import java.io.UncheckedIOException;
30import java.lang.ref.SoftReference;
31import java.net.URI;
32import java.nio.file.DirectoryStream;
33import java.nio.file.FileSystem;
34import java.nio.file.FileSystems;
35import java.nio.file.FileSystemNotFoundException;
36import java.nio.file.Files;
37import java.nio.file.Path;
38import java.nio.file.ProviderNotFoundException;
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.util.Context;
52
53/**
54 * A package-oriented index into the jrt: filesystem.
55 */
56public 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     * 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
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
146        CtSym(boolean hidden, boolean proprietary, String minProfile) {
147            this.hidden = hidden;
148            this.proprietary = proprietary;
149            this.minProfile = minProfile;
150        }
151
152        @Override
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     */
179    private JRTIndex() throws IOException {
180        jrtfs = FileSystems.getFileSystem(URI.create("jrt:/"));
181        entries = new HashMap<>();
182    }
183
184    public CtSym getCtSym(CharSequence packageName) throws IOException {
185        return getEntry(RelativeDirectory.forPackage(packageName)).ctSym;
186    }
187
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
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}
275