001/*
002 *  This file is part of the Jikes RVM project (http://jikesrvm.org).
003 *
004 *  This file is licensed to You under the Eclipse Public License (EPL);
005 *  You may not use this file except in compliance with the License. You
006 *  may obtain a copy of the License at
007 *
008 *      http://www.opensource.org/licenses/eclipse-1.0.php
009 *
010 *  See the COPYRIGHT.txt file distributed with this work for information
011 *  regarding copyright ownership.
012 */
013package org.jikesrvm.classloader;
014
015import java.io.DataInputStream;
016import java.io.File;
017import java.io.FileInputStream;
018import java.io.IOException;
019import java.io.InputStream;
020import java.net.URL;
021import java.util.Enumeration;
022import java.util.HashMap;
023import java.util.StringTokenizer;
024import java.util.Vector;
025import java.util.zip.ZipEntry;
026import java.util.zip.ZipFile;
027import org.jikesrvm.VM;
028import org.jikesrvm.runtime.Entrypoints;
029import org.jikesrvm.util.ImmutableEntryHashMapRVM;
030
031/**
032 * Implements an object that functions as the bootstrap class loader.
033 * This class is a Singleton pattern.
034 */
035public final class BootstrapClassLoader extends java.lang.ClassLoader {
036
037  private final ImmutableEntryHashMapRVM<String, RVMType> loaded =
038    new ImmutableEntryHashMapRVM<String, RVMType>();
039
040  /** Places whence we load bootstrap .class files. */
041  private static String bootstrapClasspath;
042
043  /**
044   * Set list of places to be searched for VM classes and resources.
045   * @param bootstrapClasspath path specification in standard "classpath"
046   *    format
047   */
048  public static void setBootstrapRepositories(String bootstrapClasspath) {
049    BootstrapClassLoader.bootstrapClasspath = bootstrapClasspath;
050  }
051
052  /**
053   * @return List of places to be searched for VM classes and resources,
054   *      in standard "classpath" format
055   */
056  public static String getBootstrapRepositories() {
057    return bootstrapClasspath;
058  }
059
060  /**
061   * Initialize for execution.
062   * @param bootstrapClasspath names of directories containing the bootstrap
063   * .class files, and the names of any .zip/.jar files.
064   * These are the ones that implement the VM and its
065   * standard runtime libraries.  This may contain several names separated
066   * with colons (':'), just
067   * as a classpath may.   (<code>null</code> ==&gt; use the values specified by
068   * {@link #setBootstrapRepositories} when the boot image was created.  This
069   * feature is not actually used, but may be helpful in avoiding trouble.)
070   */
071  public static void boot(String bootstrapClasspath) {
072    if (bootstrapClasspath != null) {
073      BootstrapClassLoader.bootstrapClasspath = bootstrapClasspath;
074    }
075    zipFileCache = new HashMap<String, ZipFile>();
076    if (VM.runningVM) {
077      try {
078        /* Here, we have to replace the fields that aren't carried over from
079         * boot image writing time to run time.
080         * This would be the following, if the fields weren't final:
081         *
082         * bootstrapClassLoader.definedPackages    = new HashMap();
083         */
084        Entrypoints.classLoaderDefinedPackages.setObjectValueUnchecked(bootstrapClassLoader,
085                                                                          new java.util.HashMap<String, Package>());
086      } catch (Exception e) {
087        VM.sysFail("Failed to setup bootstrap class loader");
088      }
089    }
090  }
091
092  /** Prevent other classes from constructing one. */
093  private BootstrapClassLoader() {
094    super(null);
095  }
096
097  /* Interface */
098  private static final BootstrapClassLoader bootstrapClassLoader = new BootstrapClassLoader();
099
100  public static BootstrapClassLoader getBootstrapClassLoader() {
101    return bootstrapClassLoader;
102  }
103
104  /**
105   * Backdoor for use by TypeReference.resolve when !VM.runningVM.
106   * As of this writing, it is not used by any other classes.
107   * @param className name of the class to be loaded
108   * @return type for the loaded class
109   * @throws NoClassDefFoundError when no definition of the class was found
110   */
111  synchronized RVMType loadVMClass(String className) throws NoClassDefFoundError {
112    try {
113      InputStream is = getResourceAsStream(className.replace('.', File.separatorChar) + ".class");
114      if (is == null) throw new NoClassDefFoundError(className);
115      DataInputStream dataInputStream = new DataInputStream(is);
116      RVMType type = null;
117      try {
118        // Debugging:
119        // VM.sysWriteln("loadVMClass: trying to resolve className " + className);
120        type = RVMClassLoader.defineClassInternal(className, dataInputStream, this);
121        loaded.put(className, type);
122      } finally {
123        try {
124          // Make sure the input stream is closed.
125          dataInputStream.close();
126        } catch (IOException e) { }
127      }
128      return type;
129    } catch (NoClassDefFoundError e) {
130      throw e;
131    } catch (Throwable e) {
132      // We didn't find the class, or it wasn't valid, etc.
133      NoClassDefFoundError ncdf = new NoClassDefFoundError(className);
134      ncdf.initCause(e);
135      throw ncdf;
136    }
137  }
138
139  @Override
140  public synchronized Class<?> loadClass(String className, boolean resolveClass) throws ClassNotFoundException {
141    if (!VM.runningVM) {
142      return super.loadClass(className, resolveClass);
143    }
144    if (className.startsWith("L") && className.endsWith(";")) {
145      className = className.substring(1, className.length() - 2);
146    }
147    RVMType loadedType = loaded.get(className);
148    Class<?> loadedClass;
149    if (loadedType == null) {
150      loadedClass = findClass(className);
151    } else {
152      loadedClass = loadedType.getClassForType();
153    }
154    if (resolveClass) {
155      resolveClass(loadedClass);
156    }
157    return loadedClass;
158  }
159
160  /**
161   * Search the bootstrap class loader's classpath for given class.
162   *
163   * @param className the name of the class to load
164   * @return the class object, if it was found
165   * @exception ClassNotFoundException if the class was not found, or was invalid
166   */
167  @Override
168  public Class<?> findClass(String className) throws ClassNotFoundException {
169    final boolean DBG = false;
170    if (!VM.runningVM) {
171      return super.findClass(className);
172    }
173    if (className.startsWith("[")) {
174      TypeReference typeRef =
175          TypeReference.findOrCreate(this, Atom.findOrCreateAsciiAtom(className.replace('.', '/')));
176      RVMType ans = typeRef.resolve();
177      loaded.put(className, ans);
178      return ans.getClassForType();
179    } else {
180      if (!VM.fullyBooted) {
181        VM.sysWrite("Trying to load a class (");
182        VM.sysWrite(className);
183        VM.sysWrite(") too early in the booting process, before dynamic");
184        VM.sysWriteln(" class loading is enabled; aborting.");
185        VM.sysFail("Trying to load a class too early in the booting process");
186      }
187      // class types: try to find the class file
188      try {
189        if (className.startsWith("L") && className.endsWith(";")) {
190          className = className.substring(1, className.length() - 2);
191        }
192        InputStream is = getResourceAsStream(className.replace('.', File.separatorChar) + ".class");
193        if (is == null) throw new ClassNotFoundException(className);
194        DataInputStream dataInputStream = new DataInputStream(is);
195        Class<?> cls = null;
196        try {
197          RVMType type = RVMClassLoader.defineClassInternal(className, dataInputStream, this);
198          loaded.put(className, type);
199          cls = type.getClassForType();
200        } finally {
201          try {
202            // Make sure the input stream is closed.
203            dataInputStream.close();
204          } catch (IOException e) { }
205        }
206        return cls;
207      } catch (ClassNotFoundException e) {
208        throw e;
209      } catch (Throwable e) {
210        if (DBG) {
211          VM.sysWrite("About to throw ClassNotFoundException(", className, ") because we got this Throwable:");
212          e.printStackTrace();
213        }
214        // We didn't find the class, or it wasn't valid, etc.
215        throw new ClassNotFoundException(className, e);
216      }
217    }
218  }
219
220  /** Keep this a static field, since it's looked at in
221   *  {@link MemberReference#parse}. */
222  public static final String myName = "BootstrapCL";
223
224  @Override
225  public String toString() {
226    return myName;
227  }
228
229  private static HashMap<String, ZipFile> zipFileCache;
230
231  private interface Handler<T> {
232    void process(ZipFile zf, ZipEntry ze) throws Exception;
233
234    void process(File f) throws Exception;
235
236    T getResult();
237  }
238
239  @Override
240  public InputStream getResourceAsStream(final String name) {
241    Handler<InputStream> findStream = new Handler<InputStream>() {
242      InputStream stream;
243
244      @Override
245      public InputStream getResult() {
246        return stream;
247      }
248
249      @Override
250      public void process(ZipFile zf, ZipEntry ze) throws Exception {
251        stream = zf.getInputStream(ze);
252      }
253
254      @Override
255      public void process(File file) throws Exception {
256        stream = new FileInputStream(file);
257      }
258    };
259
260    return getResourceInternal(name, findStream, false);
261  }
262
263  @Override
264  public URL findResource(final String name) {
265    Handler<URL> findURL = new Handler<URL>() {
266      URL url;
267
268      @Override
269      public URL getResult() {
270        return url;
271      }
272
273      @Override
274      public void process(ZipFile zf, ZipEntry ze) throws Exception {
275        url = new URL("jar", null, -1, "file:" + zf.getName() + "!/" + name);
276      }
277
278      @Override
279      public void process(File file) throws Exception {
280        url = new URL("file", null, -1, file.getName());
281      }
282    };
283
284    return getResourceInternal(name, findURL, false);
285  }
286
287  @Override
288  public Enumeration<URL> findResources(final String name) {
289    Handler<Enumeration<URL>> findURL = new Handler<Enumeration<URL>>() {
290      Vector<URL> urls;
291
292      @Override
293      public Enumeration<URL> getResult() {
294        if (urls == null) urls = new Vector<URL>();
295        return urls.elements();
296      }
297
298      @Override
299      public void process(ZipFile zf, ZipEntry ze) throws Exception {
300        if (urls == null) urls = new Vector<URL>();
301        urls.add(new URL("jar", null, -1, "file:" + zf.getName() + "!/" + name));
302      }
303
304      @Override
305      public void process(File file) throws Exception {
306        if (urls == null) urls = new Vector<URL>();
307        urls.add(new URL("file", null, -1, file.getName()));
308      }
309    };
310
311    return getResourceInternal(name, findURL, true);
312  }
313
314  private <T> T getResourceInternal(String name, Handler<T> h, boolean multiple) {
315    if (name.startsWith(File.separator)) {
316      name = name.substring(File.separator.length());
317    }
318
319    StringTokenizer tok = new StringTokenizer(getBootstrapRepositories(), File.pathSeparator);
320
321    while (tok.hasMoreElements()) {
322      try {
323        String path = tok.nextToken();
324        if (path.endsWith(".jar") || path.endsWith(".zip")) {
325          ZipFile zf = zipFileCache.get(path);
326          if (zf == null) {
327            zf = new ZipFile(path);
328            zipFileCache.put(path, zf);
329          }
330          // Zip spec. states that separator must be '/' in the path
331          if (File.separatorChar != '/') {
332            name = name.replace(File.separatorChar, '/');
333          }
334          ZipEntry ze = zf.getEntry(name);
335          if (ze == null) continue;
336
337          h.process(zf, ze);
338          if (!multiple) return h.getResult();
339        } else if (path.endsWith(File.separator)) {
340          File file = new File(path + name);
341          if (file.exists()) {
342            h.process(file);
343            if (!multiple) return h.getResult();
344          }
345        } else {
346          File file = new File(path + File.separator + name);
347          if (file.exists()) {
348            h.process(file);
349            if (!multiple) return h.getResult();
350          }
351        }
352      } catch (Exception e) {
353      }
354    }
355
356    return (multiple) ? h.getResult() : null;
357  }
358}