ClassFileReader.java revision 3419:d71cfeef72eb
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.ClassFile; 29import com.sun.tools.classfile.ConstantPoolException; 30import com.sun.tools.classfile.Dependencies.ClassFileError; 31 32import java.io.Closeable; 33import java.io.File; 34import java.io.FileNotFoundException; 35import java.io.IOException; 36import java.io.InputStream; 37import java.io.UncheckedIOException; 38import java.nio.file.FileSystem; 39import java.nio.file.FileSystems; 40import java.nio.file.Files; 41import java.nio.file.Path; 42import java.util.ArrayList; 43import java.util.Collections; 44import java.util.Enumeration; 45import java.util.Iterator; 46import java.util.List; 47import java.util.NoSuchElementException; 48import java.util.Set; 49import java.util.jar.JarEntry; 50import java.util.jar.JarFile; 51import java.util.stream.Collectors; 52import java.util.stream.Stream; 53 54/** 55 * ClassFileReader reads ClassFile(s) of a given path that can be 56 * a .class file, a directory, or a JAR file. 57 */ 58public class ClassFileReader implements Closeable { 59 /** 60 * Returns a ClassFileReader instance of a given path. 61 */ 62 public static ClassFileReader newInstance(Path path) throws IOException { 63 if (Files.notExists(path)) { 64 throw new FileNotFoundException(path.toString()); 65 } 66 67 if (Files.isDirectory(path)) { 68 return new DirectoryReader(path); 69 } else if (path.getFileName().toString().endsWith(".jar")) { 70 return new JarFileReader(path); 71 } else { 72 return new ClassFileReader(path); 73 } 74 } 75 76 /** 77 * Returns a ClassFileReader instance of a given JarFile. 78 */ 79 public static ClassFileReader newInstance(Path path, JarFile jf) throws IOException { 80 return new JarFileReader(path, jf); 81 } 82 83 /** 84 * Returns a ClassFileReader instance of a given FileSystem and path. 85 * 86 * This method is used for reading classes from jrtfs. 87 */ 88 public static ClassFileReader newInstance(FileSystem fs, Path path) throws IOException { 89 return new DirectoryReader(fs, path); 90 } 91 92 protected final Path path; 93 protected final String baseFileName; 94 protected Set<String> entries; // binary names 95 96 protected final List<String> skippedEntries = new ArrayList<>(); 97 protected ClassFileReader(Path path) { 98 this.path = path; 99 this.baseFileName = path.getFileName() != null 100 ? path.getFileName().toString() 101 : path.toString(); 102 } 103 104 public String getFileName() { 105 return baseFileName; 106 } 107 108 public List<String> skippedEntries() { 109 return skippedEntries; 110 } 111 112 /** 113 * Returns all entries in this archive. 114 */ 115 public Set<String> entries() { 116 Set<String> es = this.entries; 117 if (es == null) { 118 // lazily scan the entries 119 this.entries = scan(); 120 } 121 return this.entries; 122 } 123 124 /** 125 * Returns the ClassFile matching the given binary name 126 * or a fully-qualified class name. 127 */ 128 public ClassFile getClassFile(String name) throws IOException { 129 if (name.indexOf('.') > 0) { 130 int i = name.lastIndexOf('.'); 131 String pathname = name.replace('.', File.separatorChar) + ".class"; 132 if (baseFileName.equals(pathname) || 133 baseFileName.equals(pathname.substring(0, i) + "$" + 134 pathname.substring(i+1, pathname.length()))) { 135 return readClassFile(path); 136 } 137 } else { 138 if (baseFileName.equals(name.replace('/', File.separatorChar) + ".class")) { 139 return readClassFile(path); 140 } 141 } 142 return null; 143 } 144 145 public Iterable<ClassFile> getClassFiles() throws IOException { 146 return new Iterable<ClassFile>() { 147 public Iterator<ClassFile> iterator() { 148 return new FileIterator(); 149 } 150 }; 151 } 152 153 protected ClassFile readClassFile(Path p) throws IOException { 154 InputStream is = null; 155 try { 156 is = Files.newInputStream(p); 157 return ClassFile.read(is); 158 } catch (ConstantPoolException e) { 159 throw new ClassFileError(e); 160 } finally { 161 if (is != null) { 162 is.close(); 163 } 164 } 165 } 166 167 protected Set<String> scan() { 168 try { 169 ClassFile cf = ClassFile.read(path); 170 return Collections.singleton(cf.getName()); 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(f -> path.relativize(f)) 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 new Iterable<ClassFile>() { 262 public Iterator<ClassFile> iterator() { 263 return iter; 264 } 265 }; 266 } 267 268 class DirectoryIterator implements Iterator<ClassFile> { 269 private final List<Path> entries; 270 private int index = 0; 271 DirectoryIterator() throws IOException { 272 List<Path> paths = null; 273 try (Stream<Path> stream = Files.walk(path, Integer.MAX_VALUE)) { 274 paths = stream.filter(ClassFileReader::isClass) 275 .collect(Collectors.toList()); 276 } 277 this.entries = paths; 278 this.index = 0; 279 } 280 281 public boolean hasNext() { 282 return index != entries.size(); 283 } 284 285 public ClassFile next() { 286 if (!hasNext()) { 287 throw new NoSuchElementException(); 288 } 289 Path path = entries.get(index++); 290 try { 291 return readClassFile(path); 292 } catch (IOException e) { 293 throw new ClassFileError(e); 294 } 295 } 296 297 public void remove() { 298 throw new UnsupportedOperationException("Not supported yet."); 299 } 300 } 301 } 302 303 static class JarFileReader extends ClassFileReader { 304 private final JarFile jarfile; 305 JarFileReader(Path path) throws IOException { 306 this(path, new JarFile(path.toFile(), false)); 307 } 308 309 JarFileReader(Path path, JarFile jf) throws IOException { 310 super(path); 311 this.jarfile = jf; 312 } 313 314 @Override 315 public void close() throws IOException { 316 jarfile.close(); 317 } 318 319 protected Set<String> scan() { 320 try (JarFile jf = new JarFile(path.toFile())) { 321 return jf.stream().map(JarEntry::getName) 322 .filter(n -> n.endsWith(".class")) 323 .collect(Collectors.toSet()); 324 } catch (IOException e) { 325 throw new UncheckedIOException(e); 326 } 327 } 328 329 public ClassFile getClassFile(String name) throws IOException { 330 if (name.indexOf('.') > 0) { 331 int i = name.lastIndexOf('.'); 332 String entryName = name.replace('.', '/') + ".class"; 333 JarEntry e = jarfile.getJarEntry(entryName); 334 if (e == null) { 335 e = jarfile.getJarEntry(entryName.substring(0, i) + "$" 336 + entryName.substring(i + 1, entryName.length())); 337 } 338 if (e != null) { 339 return readClassFile(jarfile, e); 340 } 341 } else { 342 JarEntry e = jarfile.getJarEntry(name + ".class"); 343 if (e != null) { 344 return readClassFile(jarfile, e); 345 } 346 } 347 return null; 348 } 349 350 protected ClassFile readClassFile(JarFile jarfile, JarEntry e) throws IOException { 351 InputStream is = null; 352 try { 353 is = jarfile.getInputStream(e); 354 return ClassFile.read(is); 355 } catch (ConstantPoolException ex) { 356 throw new ClassFileError(ex); 357 } finally { 358 if (is != null) 359 is.close(); 360 } 361 } 362 363 public Iterable<ClassFile> getClassFiles() throws IOException { 364 final Iterator<ClassFile> iter = new JarFileIterator(this, jarfile); 365 return new Iterable<ClassFile>() { 366 public Iterator<ClassFile> iterator() { 367 return iter; 368 } 369 }; 370 } 371 } 372 373 class JarFileIterator implements Iterator<ClassFile> { 374 protected final JarFileReader reader; 375 protected Enumeration<JarEntry> entries; 376 protected JarFile jf; 377 protected JarEntry nextEntry; 378 protected ClassFile cf; 379 JarFileIterator(JarFileReader reader) { 380 this(reader, null); 381 } 382 JarFileIterator(JarFileReader reader, JarFile jarfile) { 383 this.reader = reader; 384 setJarFile(jarfile); 385 } 386 387 void setJarFile(JarFile jarfile) { 388 if (jarfile == null) return; 389 390 this.jf = jarfile; 391 this.entries = jf.entries(); 392 this.nextEntry = nextEntry(); 393 } 394 395 public boolean hasNext() { 396 if (nextEntry != null && cf != null) { 397 return true; 398 } 399 while (nextEntry != null) { 400 try { 401 cf = reader.readClassFile(jf, nextEntry); 402 return true; 403 } catch (ClassFileError | IOException ex) { 404 skippedEntries.add(nextEntry.getName()); 405 } 406 nextEntry = nextEntry(); 407 } 408 return false; 409 } 410 411 public ClassFile next() { 412 if (!hasNext()) { 413 throw new NoSuchElementException(); 414 } 415 ClassFile classFile = cf; 416 cf = null; 417 nextEntry = nextEntry(); 418 return classFile; 419 } 420 421 protected JarEntry nextEntry() { 422 while (entries.hasMoreElements()) { 423 JarEntry e = entries.nextElement(); 424 String name = e.getName(); 425 if (name.endsWith(".class")) { 426 return e; 427 } 428 } 429 return null; 430 } 431 432 public void remove() { 433 throw new UnsupportedOperationException("Not supported yet."); 434 } 435 } 436 private static final String MODULE_INFO = "module-info.class"; 437} 438