1 /*
   2  * Copyright (c) 2016, 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.jdeprscan.scan;
  27 
  28 import com.sun.tools.classfile.ClassFile;
  29 import com.sun.tools.classfile.ConstantPoolException;
  30 
  31 import java.io.IOException;
  32 import java.net.URI;
  33 import java.nio.file.FileSystem;
  34 import java.nio.file.FileSystems;
  35 import java.nio.file.Files;
  36 import java.nio.file.NoSuchFileException;
  37 import java.nio.file.Path;
  38 import java.nio.file.Paths;
  39 import java.util.ArrayList;
  40 import java.util.List;
  41 import java.util.Optional;
  42 import java.util.jar.JarEntry;
  43 import java.util.jar.JarFile;
  44 import java.util.stream.Stream;
  45 
  46 /**
  47  * A simple search path for classes.
  48  */
  49 public class ClassFinder {
  50     final List<PathEntry> list = new ArrayList<>();
  51     final boolean verbose;
  52 
  53     public ClassFinder(boolean verbose) {
  54         this.verbose = verbose;
  55     }
  56 
  57     /**
  58      * Adds a directory to this finder's search path, ignoring errors.
  59      *
  60      * @param dirName the directory to add
  61      */
  62     public void addDir(String dirName) {
  63         Path dir = Paths.get(dirName);
  64 
  65         if (Files.isDirectory(dir)) {
  66             list.add(new DirPathEntry(dir));
  67         }
  68     }
  69 
  70     /**
  71      * Adds a jar file to this finder's search path, ignoring errors.
  72      *
  73      * @param jarName the jar file name to add
  74      */
  75     public void addJar(String jarName) {
  76         try {
  77             list.add(new JarPathEntry(new JarFile(jarName)));
  78         } catch (IOException ignore) { }
  79     }
  80 
  81     /**
  82      * Adds the JRT filesystem to this finder's search path.
  83      */
  84     public void addJrt() {
  85         list.add(new JrtPathEntry());
  86     }
  87 
  88     /**
  89      * Searches the class path for a class with the given name,
  90      * returning a ClassFile for it. Returns null if not found.
  91      *
  92      * @param className the class to search for
  93      * @return a ClassFile instance, or null if not found
  94      */
  95     public ClassFile find(String className) {
  96         for (PathEntry pe : list) {
  97             ClassFile cf = pe.find(className);
  98             if (cf != null) {
  99                 return cf;
 100             }
 101         }
 102         return null;
 103     }
 104 
 105     /**
 106      * An entry in this finder's class path.
 107      */
 108     interface PathEntry {
 109         /**
 110          * Returns a ClassFile instance corresponding to this name,
 111          * or null if it's not present in this entry.
 112          *
 113          * @param className the class to search for
 114          * @return a ClassFile instance, or null if not found
 115          */
 116         ClassFile find(String className);
 117     }
 118 
 119     /**
 120      * An entry that represents a jar file.
 121      */
 122     class JarPathEntry implements PathEntry {
 123         final JarFile jarFile;
 124 
 125         JarPathEntry(JarFile jf) {
 126             jarFile = jf;
 127         }
 128 
 129         @Override
 130         public ClassFile find(String className) {
 131             JarEntry entry = jarFile.getJarEntry(className + ".class");
 132             if (entry == null) {
 133                 return null;
 134             }
 135             try {
 136                 return ClassFile.read(jarFile.getInputStream(entry));
 137             } catch (IOException|ConstantPoolException ex) {
 138                 if (verbose) {
 139                     ex.printStackTrace();
 140                 }
 141             }
 142             return null;
 143         }
 144     }
 145 
 146     /**
 147      * An entry that represents a directory containing a class hierarchy.
 148      */
 149     class DirPathEntry implements PathEntry {
 150         final Path dir;
 151 
 152         DirPathEntry(Path dir) {
 153             this.dir = dir;
 154         }
 155 
 156         @Override
 157         public ClassFile find(String className) {
 158             Path classFileName = dir.resolve(className + ".class");
 159             try {
 160                 return ClassFile.read(classFileName);
 161             } catch (NoSuchFileException nsfe) {
 162                 // not found, return silently
 163             } catch (IOException|ConstantPoolException ex) {
 164                 if (verbose) {
 165                     ex.printStackTrace();
 166                 }
 167             }
 168             return null;
 169         }
 170     }
 171 
 172     /**
 173      * An entry that represents the JRT filesystem in the running image.
 174      *
 175      * JRT filesystem structure is:
 176      *     /packages/<dotted-pkgname>/<modlink>
 177      * where modlink is a symbolic link to /modules/<modname> which is
 178      * the top of the usual package-class hierarchy
 179      */
 180     class JrtPathEntry implements PathEntry {
 181         final FileSystem fs = FileSystems.getFileSystem(URI.create("jrt:/"));
 182 
 183         @Override
 184         public ClassFile find(String className) {
 185             int end = className.lastIndexOf('/');
 186             if (end < 0) {
 187                 return null;
 188             }
 189             String pkg = "/packages/" + className.substring(0, end)
 190                                                  .replace('/', '.');
 191             try (Stream<Path> mods = Files.list(fs.getPath(pkg))) {
 192                 Optional<Path> opath =
 193                     mods.map(path -> path.resolve(className + ".class"))
 194                         .filter(Files::exists)
 195                         .findFirst();
 196                 if (opath.isPresent()) {
 197                     return ClassFile.read(opath.get());
 198                 } else {
 199                     return null;
 200                 }
 201             } catch (NoSuchFileException nsfe) {
 202                 // not found, return silently
 203             } catch (IOException|ConstantPoolException ex) {
 204                 if (verbose) {
 205                     ex.printStackTrace();
 206                 }
 207             }
 208             return null;
 209         }
 210     }
 211 }