PackagesHelper.java revision 1818:713ce238f9be
1228753Smm/*
2228753Smm * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
3228753Smm * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4228753Smm *
5228753Smm * This code is free software; you can redistribute it and/or modify it
6228753Smm * under the terms of the GNU General Public License version 2 only, as
7228753Smm * published by the Free Software Foundation.  Oracle designates this
8228753Smm * particular file as subject to the "Classpath" exception as provided
9228753Smm * by Oracle in the LICENSE file that accompanied this code.
10228753Smm *
11228753Smm * This code is distributed in the hope that it will be useful, but WITHOUT
12228753Smm * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13228753Smm * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14228753Smm * version 2 for more details (a copy is included in the LICENSE file that
15228753Smm * accompanied this code).
16228753Smm *
17228753Smm * You should have received a copy of the GNU General Public License version
18228753Smm * 2 along with this work; if not, write to the Free Software Foundation,
19228753Smm * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20228753Smm *
21228753Smm * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22228753Smm * or visit www.oracle.com if you need additional information or have any
23228753Smm * questions.
24228753Smm */
25228753Smm
26228753Smmpackage jdk.nashorn.tools.jjs;
27228763Smm
28228753Smmimport java.lang.reflect.Modifier;
29228753Smmimport java.io.IOException;
30232153Smmimport java.io.File;
31228753Smmimport java.net.URI;
32228753Smmimport java.nio.file.DirectoryStream;
33228753Smmimport java.nio.file.Files;
34228753Smmimport java.nio.file.FileSystem;
35232153Smmimport java.nio.file.FileSystems;
36232153Smmimport java.nio.file.Path;
37232153Smmimport java.util.ArrayList;
38232153Smmimport java.util.Collections;
39232153Smmimport java.util.EnumSet;
40232153Smmimport java.util.HashSet;
41232153Smmimport java.util.LinkedHashMap;
42232153Smmimport java.util.List;
43232153Smmimport java.util.Map;
44232153Smmimport java.util.Set;
45232153Smmimport java.util.stream.Collectors;
46232153Smmimport java.util.stream.Stream;
47232153Smmimport javax.tools.JavaCompiler;
48232153Smmimport javax.tools.JavaFileManager.Location;
49232153Smmimport javax.tools.JavaFileObject;
50232153Smmimport javax.tools.StandardJavaFileManager;
51232153Smmimport javax.tools.StandardLocation;
52232153Smmimport javax.tools.ToolProvider;
53232153Smmimport jdk.nashorn.internal.runtime.Context;
54232153Smm
55232153Smm/**
56232153Smm * A helper class to compute properties of a Java package object. Properties of
57228753Smm * package object are (simple) top level class names in that java package and
58228753Smm * immediate subpackages of that package.
59228753Smm */
60232153Smmfinal class PackagesHelper {
61228753Smm    // JavaCompiler may be null on certain platforms (eg. JRE)
62228753Smm    private static final JavaCompiler compiler;
63228753Smm    static {
64302001Smm        // Use javac only if security manager is not around!
65232153Smm        compiler = System.getSecurityManager() == null? ToolProvider.getSystemJavaCompiler() : null;
66232153Smm    }
67232153Smm
68232153Smm    /**
69232153Smm     * Is javac available?
70232153Smm     *
71232153Smm     * @return true if javac is available
72232153Smm     */
73232153Smm    private static boolean isJavacAvailable() {
74232153Smm        return compiler != null;
75342361Smm    }
76232153Smm
77232153Smm    private final Context context;
78232153Smm    private final boolean modulePathSet;
79228753Smm    private final StandardJavaFileManager fm;
80228753Smm    private final Set<JavaFileObject.Kind> fileKinds;
81228753Smm    private final FileSystem jrtfs;
82228753Smm
83228753Smm    /**
84228753Smm     * Construct a new PackagesHelper.
85228753Smm     *
86228753Smm     * @param context the current Nashorn Context
87228753Smm     */
88228753Smm    PackagesHelper(final Context context) throws IOException {
89228753Smm        this.context = context;
90        final String modulePath = context.getEnv()._module_path;
91        this.modulePathSet = modulePath != null && !modulePath.isEmpty();
92        if (isJavacAvailable()) {
93            final String classPath = context.getEnv()._classpath;
94            fm = compiler.getStandardFileManager(null, null, null);
95            fileKinds = EnumSet.of(JavaFileObject.Kind.CLASS);
96
97            if (this.modulePathSet) {
98                fm.setLocation(StandardLocation.MODULE_PATH, getFiles(modulePath));
99            }
100
101            if (classPath != null && !classPath.isEmpty()) {
102                fm.setLocation(StandardLocation.CLASS_PATH, getFiles(classPath));
103            } else {
104                // no classpath set. Make sure that it is empty and not any default like "."
105                fm.setLocation(StandardLocation.CLASS_PATH, Collections.<File>emptyList());
106            }
107            jrtfs = null;
108        } else {
109            // javac is not available - directly use jrt fs
110            // to support at least platform classes.
111            fm = null;
112            fileKinds = null;
113            jrtfs = FileSystems.getFileSystem(URI.create("jrt:/"));
114        }
115    }
116
117    // LRU cache for java package properties lists
118    private final LinkedHashMap<String, List<String>> propsCache =
119        new LinkedHashMap<String, List<String>>(32, 0.75f, true) {
120            private static final int CACHE_SIZE = 100;
121            private static final long serialVersionUID = 1;
122
123            @Override
124            protected boolean removeEldestEntry(final Map.Entry<String, List<String>> eldest) {
125                return size() > CACHE_SIZE;
126            }
127        };
128
129    /**
130     * Return the list of properties of the given Java package or package prefix
131     *
132     * @param pkg Java package name or package prefix name
133     * @return the list of properties of the given Java package or package prefix
134     */
135    List<String> getPackageProperties(final String pkg) {
136        // check the cache first
137        if (propsCache.containsKey(pkg)) {
138            return propsCache.get(pkg);
139        }
140
141        try {
142            // make sorted list of properties
143            final List<String> props = new ArrayList<>(listPackage(pkg));
144            Collections.sort(props);
145            propsCache.put(pkg, props);
146            return props;
147        } catch (final IOException exp) {
148            if (Main.DEBUG) {
149                exp.printStackTrace();
150            }
151            return Collections.<String>emptyList();
152        }
153    }
154
155    public void close() throws IOException {
156        if (fm != null) {
157            fm.close();
158        }
159    }
160
161    private Set<String> listPackage(final String pkg) throws IOException {
162        final Set<String> props = new HashSet<>();
163        if (fm != null) {
164            listPackage(StandardLocation.PLATFORM_CLASS_PATH, pkg, props);
165            if (this.modulePathSet) {
166                for (Set<Location> locs : fm.listLocationsForModules(StandardLocation.MODULE_PATH)) {
167                    for (Location loc : locs) {
168                        listPackage(loc, pkg, props);
169                    }
170                }
171            }
172            listPackage(StandardLocation.CLASS_PATH, pkg, props);
173        } else if (jrtfs != null) {
174            // look for the /packages/<package_name> directory
175            Path pkgDir = jrtfs.getPath("/packages/" + pkg);
176            if (Files.exists(pkgDir)) {
177                String pkgSlashName = pkg.replace('.', '/');
178                try (DirectoryStream<Path> ds = Files.newDirectoryStream(pkgDir)) {
179                    // it has module links under which this package occurs
180                    for (Path mod : ds) {
181                        // get the package directory under /modules
182                        Path pkgUnderMod = jrtfs.getPath(mod.toString() + "/" + pkgSlashName);
183                        try (DirectoryStream<Path> ds2 = Files.newDirectoryStream(pkgUnderMod)) {
184                            for (Path p : ds2) {
185                                String str = p.getFileName().toString();
186                                // get rid of ".class", if any
187                                if (str.endsWith(".class")) {
188                                    final String clsName = str.substring(0, str.length() - ".class".length());
189                                    if (clsName.indexOf('$') == -1 && isClassAccessible(pkg + "." + clsName)) {
190                                        props.add(str);
191                                    }
192                                } else if (isPackageAccessible(pkg + "." + str)) {
193                                    props.add(str);
194                                }
195                            }
196                        }
197                    }
198                }
199            }
200        }
201        return props;
202    }
203
204    private void listPackage(final Location loc, final String pkg, final Set<String> props)
205            throws IOException {
206        for (JavaFileObject file : fm.list(loc, pkg, fileKinds, true)) {
207            final String binaryName = fm.inferBinaryName(loc, file);
208            // does not start with the given package prefix
209            if (!binaryName.startsWith(pkg + ".")) {
210                continue;
211            }
212
213            final int nextDot = binaryName.indexOf('.', pkg.length() + 1);
214            final int start = pkg.length() + 1;
215
216            if (nextDot != -1) {
217                // subpackage - eg. "regex" for "java.util"
218                final String pkgName = binaryName.substring(start, nextDot);
219                if (isPackageAccessible(binaryName.substring(0, nextDot))) {
220                    props.add(binaryName.substring(start, nextDot));
221                }
222            } else {
223                // class - filter out nested, inner, anonymous, local classes.
224                // Dynalink supported public nested classes as properties of
225                // StaticClass object anyway. We don't want to expose those
226                // "$" internal names as properties of package object.
227
228                final String clsName = binaryName.substring(start);
229                if (clsName.indexOf('$') == -1 && isClassAccessible(binaryName)) {
230                    props.add(clsName);
231                }
232            }
233        }
234    }
235
236    // return list of File objects for the given class path
237    private static List<File> getFiles(final String classPath) {
238        return Stream.of(classPath.split(File.pathSeparator))
239                    .map(File::new)
240                    .collect(Collectors.toList());
241    }
242
243    private boolean isClassAccessible(final String className) {
244        try {
245            final Class<?> clz = context.findClass(className);
246            return Modifier.isPublic(clz.getModifiers());
247        } catch (final ClassNotFoundException cnfe) {
248        }
249        return false;
250    }
251
252    private boolean isPackageAccessible(final String pkgName) {
253        try {
254            Context.checkPackageAccess(pkgName);
255            return true;
256        } catch (final SecurityException se) {
257            return false;
258        }
259    }
260}
261