1 /*
   2  * Copyright (c) 2012, 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.*;
  31 import java.nio.file.FileVisitResult;
  32 import java.nio.file.Files;
  33 import java.nio.file.Path;
  34 import java.nio.file.SimpleFileVisitor;
  35 import java.nio.file.attribute.BasicFileAttributes;
  36 import java.util.*;
  37 import java.util.jar.JarEntry;
  38 import java.util.jar.JarFile;
  39 
  40 /**
  41  * ClassFileReader reads ClassFile(s) of a given path that can be
  42  * a .class file, a directory, or a JAR file.
  43  */
  44 public class ClassFileReader implements Iterator<ClassFile> {
  45     /**
  46      * Returns a ClassFileReader instance of a given path.
  47      */
  48     public static ClassFileReader newInstance(File path) throws IOException {
  49         if (!path.exists()) {
  50             throw new FileNotFoundException(path.getAbsolutePath());
  51         }
  52 
  53         if (path.isDirectory()) {
  54             return new DirectoryArchive(path.toPath());
  55         } else if (path.getName().endsWith(".jar")) {
  56             return new JarFileArchive(path.toPath());
  57         } else {
  58             return new ClassFileReader(path.toPath());
  59         }
  60     }
  61 
  62     protected final Path path;
  63     protected int classCount;
  64     private ClassFileReader(Path path) throws IOException {
  65         this.path = path;
  66         if (path.toFile().getName().endsWith(".class")) {
  67             this.classCount = 0;
  68         } else {
  69             this.classCount = -1;
  70         }
  71     }
  72 
  73     public String getFileName() {
  74         return path.toFile().getName();
  75     }
  76 
  77     public ClassFile getClassFile(String classname) {
  78         String fn = classname.replace('.', File.separatorChar) + ".class";
  79         if (!fn.equals(getFileName())) {
  80             return null;
  81         }
  82 
  83         try (InputStream is = Files.newInputStream(path)) {
  84             return ClassFile.read(is);
  85         } catch (IOException | ConstantPoolException e) {
  86             throw new ClassFileError(e);
  87         }
  88     }
  89 
  90     public Iterable<ClassFile> getClassFiles() {
  91         final Iterator<ClassFile> iter = this;
  92         return new Iterable<ClassFile>() {
  93             public Iterator<ClassFile> iterator() {
  94                 return iter;
  95             }
  96         };
  97     }
  98 
  99     @Override
 100     public boolean hasNext() {
 101         return classCount == 0;
 102     }
 103 
 104     @Override
 105     public ClassFile next() {
 106         if (!hasNext()) {
 107             throw new NoSuchElementException();
 108         }
 109 
 110         try (InputStream is = Files.newInputStream(path)) {
 111             classCount++;
 112             return ClassFile.read(is);
 113         } catch (IOException | ConstantPoolException e) {
 114             throw new ClassFileError(e);
 115         }
 116     }
 117 
 118     @Override
 119     public void remove() {
 120         throw new UnsupportedOperationException("Not supported yet.");
 121     }
 122 
 123     @Override
 124     public String toString() {
 125         return path.toString();
 126     }
 127 
 128     private static class DirectoryArchive extends ClassFileReader {
 129         final List<Path> classes;
 130         private int index;
 131 
 132         DirectoryArchive(Path path) throws IOException {
 133             super(path);
 134             classes = walkTree(path);
 135             index = 0;
 136         }
 137 
 138         @Override
 139         public ClassFile getClassFile(String classname) {
 140             String fn = classname.replace('.', '/') + ".class";
 141             Path p = path.resolve(fn);
 142             if (!p.toFile().exists()) {
 143                 return null;
 144             }
 145             try (InputStream is = Files.newInputStream(p)) {
 146                 return ClassFile.read(is);
 147             } catch (IOException | ConstantPoolException e) {
 148                 throw new ClassFileError(e);
 149             }
 150         }
 151 
 152         List<Path> walkTree(Path dir) throws IOException {
 153             final List<Path> files = new ArrayList<>();
 154 
 155             Files.walkFileTree(dir, new SimpleFileVisitor<Path>() {
 156                 public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
 157                         throws IOException {
 158                     if (file.toFile().getName().endsWith(".class")) {
 159                         files.add(file);
 160                     }
 161                     return FileVisitResult.CONTINUE;
 162                 }
 163 
 164                 public FileVisitResult postVisitDirectory(Path dir, IOException e)
 165                         throws IOException {
 166                     if (e == null) {
 167                         return FileVisitResult.CONTINUE;
 168                     } else {
 169                         // directory iteration failed
 170                         throw e;
 171                     }
 172                 }
 173             });
 174             return files;
 175         }
 176 
 177         @Override
 178         public boolean hasNext() {
 179             return index != classes.size();
 180         }
 181 
 182         @Override
 183         public ClassFile next() {
 184             if (!hasNext()) {
 185                 throw new NoSuchElementException();
 186             }
 187             Path path = classes.get(index++);
 188             try (InputStream is = Files.newInputStream(path)) {
 189                 classCount++;
 190                 return ClassFile.read(is);
 191             } catch (IOException | ConstantPoolException e) {
 192                 throw new ClassFileError(e);
 193             }
 194         }
 195     }
 196 
 197     private static class JarFileArchive extends ClassFileReader {
 198         final JarFile jarfile;
 199         final Enumeration<JarEntry> entries;
 200         JarEntry nextEntry;
 201 
 202         JarFileArchive(Path path) throws IOException {
 203             super(path);
 204             this.jarfile = new JarFile(path.toFile());
 205             this.entries = jarfile.entries();
 206             while (entries.hasMoreElements()) {
 207                 JarEntry e = entries.nextElement();
 208                 String name = e.getName();
 209                 if (name.endsWith(".class")) {
 210                     nextEntry = e;
 211                     break;
 212                 }
 213             }
 214         }
 215 
 216         @Override
 217         public ClassFile getClassFile(String classname) {
 218             String fn = classname.replace('.', '/') + ".class";
 219             JarEntry e = jarfile.getJarEntry(fn);
 220             if (e == null) {
 221                 return null;
 222             }
 223 
 224             try (InputStream is = jarfile.getInputStream(e)) {
 225                 classCount++;
 226                 return ClassFile.read(is);
 227             } catch (IOException | ConstantPoolException ex) {
 228                 throw new ClassFileError(ex);
 229             }
 230         }
 231 
 232         @Override
 233         public boolean hasNext() {
 234             return nextEntry != null;
 235         }
 236 
 237         @Override
 238         public ClassFile next() {
 239             if (!hasNext()) {
 240                 throw new NoSuchElementException();
 241             }
 242 
 243             ClassFile cf;
 244             try {
 245                 cf = ClassFile.read(jarfile.getInputStream(nextEntry));
 246             } catch (IOException | ConstantPoolException ex) {
 247                 throw new ClassFileError(ex);
 248             }
 249             JarEntry entry = nextEntry;
 250             nextEntry = null;
 251             while (entries.hasMoreElements()) {
 252                 JarEntry e = entries.nextElement();
 253                 String name = e.getName();
 254                 if (name.endsWith(".class")) {
 255                     nextEntry = e;
 256                     break;
 257                 }
 258             }
 259             return cf;
 260         }
 261     }
 262 }