Module.java revision 3294:9adfb22ff08f
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 */ 25 26package com.sun.tools.jdeps; 27 28import java.io.BufferedReader; 29import java.io.IOException; 30import java.io.InputStream; 31import java.io.InputStreamReader; 32import java.io.UncheckedIOException; 33import java.lang.module.ModuleDescriptor; 34import java.net.URI; 35import java.util.Arrays; 36import java.util.Collections; 37import java.util.HashMap; 38import java.util.HashSet; 39import java.util.List; 40import java.util.Map; 41import java.util.Objects; 42import java.util.Optional; 43import java.util.Set; 44import java.util.jar.JarEntry; 45import java.util.jar.JarFile; 46import java.util.stream.Collectors; 47 48/** 49 * JDeps internal representation of module for dependency analysis. 50 */ 51class Module extends Archive { 52 static final boolean traceOn = Boolean.getBoolean("jdeps.debug"); 53 static void trace(String fmt, Object... args) { 54 if (traceOn) { 55 System.err.format(fmt, args); 56 } 57 } 58 59 /* 60 * Returns true if the given package name is JDK critical internal API 61 * in jdk.unsupported module 62 */ 63 static boolean isJDKUnsupported(Module m, String pn) { 64 return JDK_UNSUPPORTED.equals(m.name()) || unsupported.contains(pn); 65 }; 66 67 protected final ModuleDescriptor descriptor; 68 protected final Map<String, Boolean> requires; 69 protected final Map<String, Set<String>> exports; 70 protected final Set<String> packages; 71 protected final boolean isJDK; 72 protected final URI location; 73 74 private Module(String name, 75 URI location, 76 ModuleDescriptor descriptor, 77 Map<String, Boolean> requires, 78 Map<String, Set<String>> exports, 79 Set<String> packages, 80 boolean isJDK, 81 ClassFileReader reader) { 82 super(name, location, reader); 83 this.descriptor = descriptor; 84 this.location = location; 85 this.requires = Collections.unmodifiableMap(requires); 86 this.exports = Collections.unmodifiableMap(exports); 87 this.packages = Collections.unmodifiableSet(packages); 88 this.isJDK = isJDK; 89 } 90 91 /** 92 * Returns module name 93 */ 94 public String name() { 95 return descriptor.name(); 96 } 97 98 public boolean isNamed() { 99 return true; 100 } 101 102 public boolean isAutomatic() { 103 return descriptor.isAutomatic(); 104 } 105 106 public Module getModule() { 107 return this; 108 } 109 110 public ModuleDescriptor descriptor() { 111 return descriptor; 112 } 113 114 public boolean isJDK() { 115 return isJDK; 116 } 117 118 public Map<String, Boolean> requires() { 119 return requires; 120 } 121 122 public Map<String, Set<String>> exports() { 123 return exports; 124 } 125 126 public Map<String, Set<String>> provides() { 127 return descriptor.provides().entrySet().stream() 128 .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().providers())); 129 } 130 131 public Set<String> packages() { 132 return packages; 133 } 134 135 /** 136 * Tests if the package of the given name is exported. 137 */ 138 public boolean isExported(String pn) { 139 return exports.containsKey(pn) ? exports.get(pn).isEmpty() : false; 140 } 141 142 /** 143 * Converts this module to a strict module with the given dependences 144 * 145 * @throws IllegalArgumentException if this module is not an automatic module 146 */ 147 public Module toStrictModule(Map<String, Boolean> requires) { 148 if (!isAutomatic()) { 149 throw new IllegalArgumentException(name() + " already a strict module"); 150 } 151 return new StrictModule(this, requires); 152 } 153 154 /** 155 * Tests if the package of the given name is qualifiedly exported 156 * to the target. 157 */ 158 public boolean isExported(String pn, String target) { 159 return isExported(pn) || exports.containsKey(pn) && exports.get(pn).contains(target); 160 } 161 162 private final static String JDK_UNSUPPORTED = "jdk.unsupported"; 163 164 // temporary until jdk.unsupported module 165 private final static List<String> unsupported = Arrays.asList("sun.misc", "sun.reflect"); 166 167 @Override 168 public String toString() { 169 return name(); 170 } 171 172 public final static class Builder { 173 final String name; 174 final Map<String, Boolean> requires = new HashMap<>(); 175 final Map<String, Set<String>> exports = new HashMap<>(); 176 final Set<String> packages = new HashSet<>(); 177 final boolean isJDK; 178 ClassFileReader reader; 179 ModuleDescriptor descriptor; 180 URI location; 181 182 public Builder(String name) { 183 this(name, false); 184 } 185 186 public Builder(String name, boolean isJDK) { 187 this.name = name; 188 this.isJDK = isJDK; 189 } 190 191 public Builder location(URI location) { 192 this.location = location; 193 return this; 194 } 195 196 public Builder descriptor(ModuleDescriptor md) { 197 this.descriptor = md; 198 return this; 199 } 200 201 public Builder require(String d, boolean reexport) { 202 requires.put(d, reexport); 203 return this; 204 } 205 206 public Builder packages(Set<String> pkgs) { 207 packages.addAll(pkgs); 208 return this; 209 } 210 211 public Builder export(String p, Set<String> ms) { 212 Objects.requireNonNull(p); 213 Objects.requireNonNull(ms); 214 exports.put(p, new HashSet<>(ms)); 215 return this; 216 } 217 public Builder classes(ClassFileReader reader) { 218 this.reader = reader; 219 return this; 220 } 221 222 public Module build() { 223 if (descriptor.isAutomatic() && isJDK) { 224 throw new InternalError("JDK module: " + name + " can't be automatic module"); 225 } 226 227 return new Module(name, location, descriptor, requires, exports, packages, isJDK, reader); 228 } 229 } 230 231 final static Module UNNAMED_MODULE = new UnnamedModule(); 232 private static class UnnamedModule extends Module { 233 private UnnamedModule() { 234 super("unnamed", null, null, 235 Collections.emptyMap(), 236 Collections.emptyMap(), 237 Collections.emptySet(), 238 false, null); 239 } 240 241 @Override 242 public String name() { 243 return "unnamed"; 244 } 245 246 @Override 247 public boolean isNamed() { 248 return false; 249 } 250 251 @Override 252 public boolean isAutomatic() { 253 return false; 254 } 255 256 @Override 257 public boolean isExported(String pn) { 258 return true; 259 } 260 } 261 262 private static class StrictModule extends Module { 263 private static final String SERVICES_PREFIX = "META-INF/services/"; 264 private final Map<String, Set<String>> provides; 265 private final Module module; 266 private final JarFile jarfile; 267 268 /** 269 * Converts the given automatic module to a strict module. 270 * 271 * Replace this module's dependences with the given requires and also 272 * declare service providers, if specified in META-INF/services configuration file 273 */ 274 private StrictModule(Module m, Map<String, Boolean> requires) { 275 super(m.name(), m.location, m.descriptor, requires, m.exports, m.packages, m.isJDK, m.reader()); 276 this.module = m; 277 try { 278 this.jarfile = new JarFile(m.path().toFile(), false); 279 } catch (IOException e) { 280 throw new UncheckedIOException(e); 281 } 282 this.provides = providers(jarfile); 283 } 284 285 @Override 286 public Map<String, Set<String>> provides() { 287 return provides; 288 } 289 290 private Map<String, Set<String>> providers(JarFile jf) { 291 Map<String, Set<String>> provides = new HashMap<>(); 292 // map names of service configuration files to service names 293 Set<String> serviceNames = jf.stream() 294 .map(e -> e.getName()) 295 .filter(e -> e.startsWith(SERVICES_PREFIX)) 296 .distinct() 297 .map(this::toServiceName) 298 .filter(Optional::isPresent) 299 .map(Optional::get) 300 .collect(Collectors.toSet()); 301 302 // parse each service configuration file 303 for (String sn : serviceNames) { 304 JarEntry entry = jf.getJarEntry(SERVICES_PREFIX + sn); 305 Set<String> providerClasses = new HashSet<>(); 306 try (InputStream in = jf.getInputStream(entry)) { 307 BufferedReader reader 308 = new BufferedReader(new InputStreamReader(in, "UTF-8")); 309 String cn; 310 while ((cn = nextLine(reader)) != null) { 311 if (isJavaIdentifier(cn)) { 312 providerClasses.add(cn); 313 } 314 } 315 } catch (IOException e) { 316 throw new UncheckedIOException(e); 317 } 318 if (!providerClasses.isEmpty()) 319 provides.put(sn, providerClasses); 320 } 321 322 return provides; 323 } 324 325 /** 326 * Returns a container with the service type corresponding to the name of 327 * a services configuration file. 328 * 329 * For example, if called with "META-INF/services/p.S" then this method 330 * returns a container with the value "p.S". 331 */ 332 private Optional<String> toServiceName(String cf) { 333 assert cf.startsWith(SERVICES_PREFIX); 334 int index = cf.lastIndexOf("/") + 1; 335 if (index < cf.length()) { 336 String prefix = cf.substring(0, index); 337 if (prefix.equals(SERVICES_PREFIX)) { 338 String sn = cf.substring(index); 339 if (isJavaIdentifier(sn)) 340 return Optional.of(sn); 341 } 342 } 343 return Optional.empty(); 344 } 345 346 /** 347 * Reads the next line from the given reader and trims it of comments and 348 * leading/trailing white space. 349 * 350 * Returns null if the reader is at EOF. 351 */ 352 private String nextLine(BufferedReader reader) throws IOException { 353 String ln = reader.readLine(); 354 if (ln != null) { 355 int ci = ln.indexOf('#'); 356 if (ci >= 0) 357 ln = ln.substring(0, ci); 358 ln = ln.trim(); 359 } 360 return ln; 361 } 362 363 /** 364 * Returns {@code true} if the given identifier is a legal Java identifier. 365 */ 366 private static boolean isJavaIdentifier(String id) { 367 int n = id.length(); 368 if (n == 0) 369 return false; 370 if (!Character.isJavaIdentifierStart(id.codePointAt(0))) 371 return false; 372 int cp = id.codePointAt(0); 373 int i = Character.charCount(cp); 374 for (; i < n; i += Character.charCount(cp)) { 375 cp = id.codePointAt(i); 376 if (!Character.isJavaIdentifierPart(cp) && id.charAt(i) != '.') 377 return false; 378 } 379 if (cp == '.') 380 return false; 381 382 return true; 383 } 384 } 385} 386