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