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