1 /* 2 * Copyright (c) 2012, 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 26 package com.sun.tools.jdeps; 27 28 import com.sun.tools.classfile.AccessFlags; 29 import com.sun.tools.classfile.ClassFile; 30 import com.sun.tools.classfile.ConstantPoolException; 31 import com.sun.tools.classfile.Dependencies.ClassFileError; 32 33 import java.io.Closeable; 34 import java.io.File; 35 import java.io.FileNotFoundException; 36 import java.io.IOException; 37 import java.io.InputStream; 38 import java.io.UncheckedIOException; 39 import java.nio.file.FileSystem; 40 import java.nio.file.FileSystems; 41 import java.nio.file.Files; 42 import java.nio.file.Path; 43 import java.util.ArrayList; 44 import java.util.Collections; 45 import java.util.Enumeration; 46 import java.util.Iterator; 47 import java.util.List; 48 import java.util.NoSuchElementException; 49 import java.util.Set; 50 import java.util.jar.JarEntry; 51 import java.util.jar.JarFile; 52 import java.util.stream.Collectors; 53 import java.util.stream.Stream; 54 import java.util.zip.ZipFile; 55 56 /** 57 * ClassFileReader reads ClassFile(s) of a given path that can be 58 * a .class file, a directory, or a JAR file. 59 */ 60 public class ClassFileReader implements Closeable { 61 /** 62 * Returns a ClassFileReader instance of a given path. 63 */ 64 public static ClassFileReader newInstance(Path path) throws IOException { 65 return newInstance(path, null); 66 } 67 68 /** 69 * Returns a ClassFileReader instance of a given path. 70 */ 71 public static ClassFileReader newInstance(Path path, Runtime.Version version) throws IOException { 72 if (Files.notExists(path)) { 73 throw new FileNotFoundException(path.toString()); 74 } 75 76 if (Files.isDirectory(path)) { 77 return new DirectoryReader(path); 78 } else if (path.getFileName().toString().endsWith(".jar")) { 79 return new JarFileReader(path, version); 80 } else { 81 return new ClassFileReader(path); 82 } 83 } 84 85 /** 86 * Returns a ClassFileReader instance of a given FileSystem and path. 87 * 88 * This method is used for reading classes from jrtfs. 89 */ 90 public static ClassFileReader newInstance(FileSystem fs, Path path) throws IOException { 91 return new DirectoryReader(fs, path); 92 } 93 94 protected final Path path; 95 protected final String baseFileName; 96 protected Set<String> entries; // binary names 97 98 protected final List<String> skippedEntries = new ArrayList<>(); 99 protected ClassFileReader(Path path) { 100 this.path = path; 101 this.baseFileName = path.getFileName() != null 102 ? path.getFileName().toString() 103 : path.toString(); 104 } 105 106 public String getFileName() { 107 return baseFileName; 108 } 109 110 public List<String> skippedEntries() { 111 return skippedEntries; 112 } 113 114 /** 115 * Returns all entries in this archive. 116 */ 117 public Set<String> entries() { 118 Set<String> es = this.entries; 119 if (es == null) { 120 // lazily scan the entries 121 this.entries = scan(); 122 } 123 return this.entries; 124 } 125 126 /** 127 * Returns the ClassFile matching the given binary name 128 * or a fully-qualified class name. 129 */ 130 public ClassFile getClassFile(String name) throws IOException { 131 if (name.indexOf('.') > 0) { 132 int i = name.lastIndexOf('.'); 133 String pathname = name.replace('.', File.separatorChar) + ".class"; 134 if (baseFileName.equals(pathname) || 135 baseFileName.equals(pathname.substring(0, i) + "$" + 136 pathname.substring(i+1, pathname.length()))) { 137 return readClassFile(path); 138 } 139 } else { 140 if (baseFileName.equals(name.replace('/', File.separatorChar) + ".class")) { 141 return readClassFile(path); 142 } 143 } 144 return null; 145 } 146 147 public Iterable<ClassFile> getClassFiles() throws IOException { 148 return FileIterator::new; 149 } 150 151 protected ClassFile readClassFile(Path p) throws IOException { 152 InputStream is = null; 153 try { 154 is = Files.newInputStream(p); 155 return ClassFile.read(is); 156 } catch (ConstantPoolException e) { 157 throw new ClassFileError(e); 158 } finally { 159 if (is != null) { 160 is.close(); 161 } 162 } 163 } 164 165 protected Set<String> scan() { 166 try { 167 ClassFile cf = ClassFile.read(path); 168 String name = cf.access_flags.is(AccessFlags.ACC_MODULE) 169 ? "module-info" : cf.getName(); 170 return Collections.singleton(name); 171 } catch (ConstantPoolException|IOException e) { 172 throw new ClassFileError(e); 173 } 174 } 175 176 static boolean isClass(Path file) { 177 String fn = file.getFileName().toString(); 178 return fn.endsWith(".class"); 179 } 180 181 @Override 182 public void close() throws IOException { 183 } 184 185 class FileIterator implements Iterator<ClassFile> { 186 int count; 187 FileIterator() { 188 this.count = 0; 189 } 190 public boolean hasNext() { 191 return count == 0 && baseFileName.endsWith(".class"); 192 } 193 194 public ClassFile next() { 195 if (!hasNext()) { 196 throw new NoSuchElementException(); 197 } 198 try { 199 ClassFile cf = readClassFile(path); 200 count++; 201 return cf; 202 } catch (IOException e) { 203 throw new ClassFileError(e); 204 } 205 } 206 207 public void remove() { 208 throw new UnsupportedOperationException("Not supported yet."); 209 } 210 } 211 212 public String toString() { 213 return path.toString(); 214 } 215 216 private static class DirectoryReader extends ClassFileReader { 217 protected final String fsSep; 218 DirectoryReader(Path path) throws IOException { 219 this(FileSystems.getDefault(), path); 220 } 221 DirectoryReader(FileSystem fs, Path path) throws IOException { 222 super(path); 223 this.fsSep = fs.getSeparator(); 224 } 225 226 protected Set<String> scan() { 227 try (Stream<Path> stream = Files.walk(path, Integer.MAX_VALUE)) { 228 return stream.filter(ClassFileReader::isClass) 229 .map(path::relativize) 230 .map(Path::toString) 231 .map(p -> p.replace(File.separatorChar, '/')) 232 .collect(Collectors.toSet()); 233 } catch (IOException e) { 234 throw new UncheckedIOException(e); 235 } 236 } 237 238 public ClassFile getClassFile(String name) throws IOException { 239 if (name.indexOf('.') > 0) { 240 int i = name.lastIndexOf('.'); 241 String pathname = name.replace(".", fsSep) + ".class"; 242 Path p = path.resolve(pathname); 243 if (Files.notExists(p)) { 244 p = path.resolve(pathname.substring(0, i) + "$" + 245 pathname.substring(i+1, pathname.length())); 246 } 247 if (Files.exists(p)) { 248 return readClassFile(p); 249 } 250 } else { 251 Path p = path.resolve(name + ".class"); 252 if (Files.exists(p)) { 253 return readClassFile(p); 254 } 255 } 256 return null; 257 } 258 259 public Iterable<ClassFile> getClassFiles() throws IOException { 260 final Iterator<ClassFile> iter = new DirectoryIterator(); 261 return () -> iter; 262 } 263 264 class DirectoryIterator implements Iterator<ClassFile> { 265 private final List<Path> entries; 266 private int index = 0; 267 DirectoryIterator() throws IOException { 268 List<Path> paths = null; 269 try (Stream<Path> stream = Files.walk(path, Integer.MAX_VALUE)) { 270 paths = stream.filter(ClassFileReader::isClass) 271 .collect(Collectors.toList()); 272 } 273 this.entries = paths; 274 this.index = 0; 275 } 276 277 public boolean hasNext() { 278 return index != entries.size(); 279 } 280 281 public ClassFile next() { 282 if (!hasNext()) { 283 throw new NoSuchElementException(); 284 } 285 Path path = entries.get(index++); 286 try { 287 return readClassFile(path); 288 } catch (IOException e) { 289 throw new ClassFileError(e); 290 } 291 } 292 293 public void remove() { 294 throw new UnsupportedOperationException("Not supported yet."); 295 } 296 } 297 } 298 299 static class JarFileReader extends ClassFileReader { 300 private final JarFile jarfile; 301 private final Runtime.Version version; 302 303 JarFileReader(Path path, Runtime.Version version) throws IOException { 304 this(path, openJarFile(path.toFile(), version), version); 305 } 306 307 JarFileReader(Path path, JarFile jf, Runtime.Version version) throws IOException { 308 super(path); 309 this.jarfile = jf; 310 this.version = version; 311 } 312 313 @Override 314 public void close() throws IOException { 315 jarfile.close(); 316 } 317 318 private static JarFile openJarFile(File f, Runtime.Version version) 319 throws IOException { 320 JarFile jf; 321 if (version == null) { 322 jf = new JarFile(f, false); 323 if (jf.isMultiRelease()) { 324 throw new MultiReleaseException("err.multirelease.option.notfound", f.getName()); 325 } 326 } else { 327 jf = new JarFile(f, false, ZipFile.OPEN_READ, version); 328 if (!jf.isMultiRelease()) { 329 throw new MultiReleaseException("err.multirelease.option.exists", f.getName()); 330 } 331 } 332 return jf; 333 } 334 335 protected Set<String> scan() { 336 try (JarFile jf = openJarFile(path.toFile(), version)) { 337 return jf.versionedStream().map(JarEntry::getName) 338 .filter(n -> n.endsWith(".class")) 339 .collect(Collectors.toSet()); 340 } catch (IOException e) { 341 throw new UncheckedIOException(e); 342 } 343 } 344 345 public ClassFile getClassFile(String name) throws IOException { 346 if (name.indexOf('.') > 0) { 347 int i = name.lastIndexOf('.'); 348 String entryName = name.replace('.', '/') + ".class"; 349 JarEntry e = jarfile.getJarEntry(entryName); 350 if (e == null) { 351 e = jarfile.getJarEntry(entryName.substring(0, i) + "$" 352 + entryName.substring(i + 1, entryName.length())); 353 } 354 if (e != null) { 355 return readClassFile(jarfile, e); 356 } 357 } else { 358 JarEntry e = jarfile.getJarEntry(name + ".class"); 359 if (e != null) { 360 return readClassFile(jarfile, e); 361 } 362 } 363 return null; 364 } 365 366 protected ClassFile readClassFile(JarFile jarfile, JarEntry e) throws IOException { 367 try (InputStream is = jarfile.getInputStream(e)) { 368 ClassFile cf = ClassFile.read(is); 369 if (jarfile.isMultiRelease()) { 370 VersionHelper.add(jarfile, e, cf); 371 } 372 return cf; 373 } catch (ConstantPoolException ex) { 374 throw new ClassFileError(ex); 375 } 376 } 377 378 public Iterable<ClassFile> getClassFiles() throws IOException { 379 final Iterator<ClassFile> iter = new JarFileIterator(this, jarfile); 380 return () -> iter; 381 } 382 } 383 384 class JarFileIterator implements Iterator<ClassFile> { 385 protected final JarFileReader reader; 386 protected Iterator<JarEntry> entries; 387 protected JarFile jf; 388 protected JarEntry nextEntry; 389 protected ClassFile cf; 390 JarFileIterator(JarFileReader reader) { 391 this(reader, null); 392 } 393 JarFileIterator(JarFileReader reader, JarFile jarfile) { 394 this.reader = reader; 395 setJarFile(jarfile); 396 } 397 398 void setJarFile(JarFile jarfile) { 399 if (jarfile == null) return; 400 401 this.jf = jarfile; 402 this.entries = jarfile.versionedStream().iterator(); 403 this.nextEntry = nextEntry(); 404 } 405 406 public boolean hasNext() { 407 if (nextEntry != null && cf != null) { 408 return true; 409 } 410 while (nextEntry != null) { 411 try { 412 cf = reader.readClassFile(jf, nextEntry); 413 return true; 414 } catch (ClassFileError | IOException ex) { 415 skippedEntries.add(String.format("%s: %s (%s)", 416 ex.getMessage(), 417 nextEntry.getName(), 418 jf.getName())); 419 } 420 nextEntry = nextEntry(); 421 } 422 return false; 423 } 424 425 public ClassFile next() { 426 if (!hasNext()) { 427 throw new NoSuchElementException(); 428 } 429 ClassFile classFile = cf; 430 cf = null; 431 nextEntry = nextEntry(); 432 return classFile; 433 } 434 435 protected JarEntry nextEntry() { 436 while (entries.hasNext()) { 437 JarEntry e = entries.next(); 438 String name = e.getName(); 439 if (name.endsWith(".class")) { 440 return e; 441 } 442 } 443 return null; 444 } 445 446 public void remove() { 447 throw new UnsupportedOperationException("Not supported yet."); 448 } 449 } 450 private static final String MODULE_INFO = "module-info.class"; 451 }