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 }