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