PackagesHelper.java revision 1790:785843878cf7
1/* 2 * Copyright (c) 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 jdk.nashorn.tools.jjs; 27 28import java.lang.reflect.Modifier; 29import java.io.IOException; 30import java.io.File; 31import java.net.URI; 32import java.nio.file.DirectoryStream; 33import java.nio.file.Files; 34import java.nio.file.FileSystem; 35import java.nio.file.FileSystems; 36import java.nio.file.Path; 37import java.util.ArrayList; 38import java.util.Collections; 39import java.util.EnumSet; 40import java.util.HashSet; 41import java.util.LinkedHashMap; 42import java.util.List; 43import java.util.Map; 44import java.util.Set; 45import java.util.stream.Collectors; 46import java.util.stream.Stream; 47import javax.tools.JavaCompiler; 48import javax.tools.JavaFileManager.Location; 49import javax.tools.JavaFileObject; 50import javax.tools.StandardJavaFileManager; 51import javax.tools.StandardLocation; 52import javax.tools.ToolProvider; 53import jdk.nashorn.internal.runtime.Context; 54 55/** 56 * A helper class to compute properties of a Java package object. Properties of 57 * package object are (simple) top level class names in that java package and 58 * immediate subpackages of that package. 59 */ 60final class PackagesHelper { 61 // JavaCompiler may be null on certain platforms (eg. JRE) 62 private static final JavaCompiler compiler; 63 static { 64 // Use javac only if security manager is not around! 65 compiler = System.getSecurityManager() == null? ToolProvider.getSystemJavaCompiler() : null; 66 } 67 68 /** 69 * Is javac available? 70 * 71 * @return true if javac is available 72 */ 73 private static boolean isJavacAvailable() { 74 return compiler != null; 75 } 76 77 private final Context context; 78 private final boolean modulePathSet; 79 private final StandardJavaFileManager fm; 80 private final Set<JavaFileObject.Kind> fileKinds; 81 private final FileSystem jrtfs; 82 83 /** 84 * Construct a new PackagesHelper. 85 * 86 * @param context the current Nashorn Context 87 */ 88 PackagesHelper(final Context context) throws IOException { 89 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.listModuleLocations(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