1 /*
   2  * $Id$
   3  *
   4  * Copyright (c) 1996, 2009, Oracle and/or its affiliates. All rights reserved.
   5  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   6  *
   7  * This code is free software; you can redistribute it and/or modify it
   8  * under the terms of the GNU General Public License version 2 only, as
   9  * published by the Free Software Foundation.  Oracle designates this
  10  * particular file as subject to the "Classpath" exception as provided
  11  * by Oracle in the LICENSE file that accompanied this code.
  12  *
  13  * This code is distributed in the hope that it will be useful, but WITHOUT
  14  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  15  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  16  * version 2 for more details (a copy is included in the LICENSE file that
  17  * accompanied this code).
  18  *
  19  * You should have received a copy of the GNU General Public License version
  20  * 2 along with this work; if not, write to the Free Software Foundation,
  21  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  22  *
  23  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  24  * or visit www.oracle.com if you need additional information or have any
  25  * questions.
  26  */
  27 package com.sun.javatest.util;
  28 
  29 import java.io.File;
  30 import java.io.FileInputStream;
  31 import java.io.InputStream;
  32 import java.io.IOException;
  33 import java.util.Hashtable;
  34 import java.util.Map;
  35 import java.util.Vector;
  36 import java.util.zip.ZipEntry;
  37 import java.util.zip.ZipFile;
  38 
  39 /**
  40  * A class loader for loading classes from a path of directories,
  41  * zip files and jar files.
  42  */
  43 public class PathClassLoader extends ClassLoader
  44 {
  45     /**
  46      * Create a PathClassLoader, specifying a path.
  47      * @param pathString a string containing a sequence of
  48      *   file paths separated by the platform-specific file
  49      *   separator, identifying a sequence of locations in which
  50      *   to look for classes to be loaded
  51      * @see File#pathSeparator
  52      */
  53     public PathClassLoader(String pathString) {
  54         this.path = split(pathString);
  55     }
  56 
  57     /**
  58      * Create a PathClassLoader, specifying a path and a
  59      * base directory for any relative files on the path.
  60      * @param baseDir the base directory for any relative
  61      * files on the path
  62      * @param pathString a string containing a sequence of
  63      *   file paths separated by the platform-specific file
  64      *   separator, identifying a sequence of locations in which
  65      *   to look for classes to be loaded
  66      * @see File#pathSeparator
  67      */
  68     public PathClassLoader(File baseDir, String pathString) {
  69         path = split(pathString);
  70         for (int i = 0; i < path.length; i++) {
  71             File f = path[i];
  72             if (!f.isAbsolute())
  73                 path[i] = new File(baseDir, f.getPath());
  74         }
  75     }
  76 
  77     /**
  78      * Create a PathCloader, specifying an array of files for the path.
  79      * @param path an array of files, identifying a sequence of locations in which
  80      *   to look for classes to be loaded
  81      */
  82     public PathClassLoader(File[] path) {
  83         this.path = path;
  84     }
  85 
  86     /**
  87      * Attempt to load a class if it is not already loaded, and optionally
  88      * resolve any imports it might have.
  89      *
  90      * @param name The fully-qualified name of the class to load.
  91      * @param resolve True if imports should be resolved, false otherwise.
  92      * @return the class that was loaded
  93      * @throws ClassNotFoundException if the class was not found.
  94      */
  95     protected Class<?> loadClass(String name, boolean resolve)
  96         throws ClassNotFoundException {
  97 
  98             Class<?> cl = classes.get(name);
  99 
 100             if (cl == null) {
 101                 try {
 102                     cl = findSystemClass(name);
 103                 }
 104                 catch (ClassNotFoundException e) {
 105                     cl = locateClass(name);
 106                 }
 107             }
 108 
 109             if (resolve)
 110                 resolveClass(cl);
 111 
 112             return cl;
 113     }
 114 
 115 
 116     private synchronized Class<?> locateClass(String name)
 117         throws ClassNotFoundException {
 118         //System.err.println("locateClass: " + name);
 119         Class<?> c = classes.get(name);
 120         if (c != null)
 121             return c;
 122 
 123         for (int i = 0; i < path.length; i++) {
 124             if (path[i].isDirectory())
 125                 c = locateClassInDir(name, path[i]);
 126             else
 127                 c = locateClassInJar(name, path[i]);
 128 
 129             if (c != null) {
 130                 classes.put(name, c);
 131                 return c;
 132             }
 133         }
 134 
 135         throw new ClassNotFoundException(name);
 136     }
 137 
 138     private Class<?> locateClassInDir(String name, File dir)
 139         throws ClassNotFoundException {
 140         //System.err.println("locateClassInDir: " + name + " " + dir);
 141         String cname = name.replace('.', '/') + ".class";
 142         try {
 143             File file = new File(dir, cname);
 144             return readClass(name, new FileInputStream(file), (int)(file.length()));
 145         }
 146         catch (IOException e) {
 147             //System.err.println("locateClassInDir: " + e);
 148             return null;
 149         }
 150     }
 151 
 152     private Class<?> locateClassInJar(String name, File jarFile)
 153         throws ClassNotFoundException {
 154         //System.err.println("locateClassInJar: " + name + " " + jarFile);
 155         String cname = name.replace('.', '/') + ".class";
 156         try {
 157             ZipFile z = zips.get(jarFile);
 158             if (z == null) {
 159                 z = new ZipFile(jarFile);
 160                 zips.put(jarFile, z);
 161             }
 162             ZipEntry ze = z.getEntry(cname);
 163             if (ze == null)
 164                 return null;
 165             return readClass(name, z.getInputStream(ze), (int)(ze.getSize()));
 166         }
 167         catch (IOException e) {
 168             //System.err.println("locateClassInJar: " + e);
 169             return null;
 170         }
 171     }
 172 
 173     private Class<?> readClass(String name, InputStream in, int size) throws IOException {
 174         byte[] data = new byte[size];
 175         try {
 176             for (int total = 0; total < size; ) {
 177                 total += in.read(data, total, size - total);
 178             }
 179         }
 180         finally {
 181             in.close();
 182         }
 183         return defineClass(name, data, 0, data.length);
 184     }
 185 
 186     private File[] split(String s) {
 187         char pathCh = File.pathSeparatorChar;
 188         Vector<File> v = new Vector<>();
 189         int start = 0;
 190         for (int i = s.indexOf(pathCh); i != -1; i = s.indexOf(pathCh, start)) {
 191             add(s.substring(start, i), v);
 192             start = i + 1;
 193         }
 194         if (start != s.length())
 195             add(s.substring(start), v);
 196         File[] path = new File[v.size()];
 197         v.copyInto(path);
 198         return path;
 199     }
 200 
 201     private void add(String s, Vector<File> v) {
 202         if (s.length() != 0)
 203             v.addElement(new File(s));
 204     }
 205 
 206     private File[] path;
 207     private Map<String, Class<?>> classes = new Hashtable<>();
 208     private Map<File, ZipFile> zips = new Hashtable<>();
 209 }