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     */
013    package org.jikesrvm.classloader;
014    
015    import java.io.DataInputStream;
016    import java.io.File;
017    import java.io.FileInputStream;
018    import java.io.IOException;
019    import java.io.InputStream;
020    import java.net.URL;
021    import java.util.Enumeration;
022    import java.util.HashMap;
023    import java.util.StringTokenizer;
024    import java.util.Vector;
025    import java.util.zip.ZipEntry;
026    import java.util.zip.ZipFile;
027    import org.jikesrvm.VM;
028    import org.jikesrvm.runtime.Entrypoints;
029    import 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     */
035    public 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> ==> 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       * @throws NoClassDefFoundError
108       */
109      synchronized RVMType loadVMClass(String className) throws NoClassDefFoundError {
110        try {
111          InputStream is = getResourceAsStream(className.replace('.', File.separatorChar) + ".class");
112          if (is == null) throw new NoClassDefFoundError(className);
113          DataInputStream dataInputStream = new DataInputStream(is);
114          RVMType type = null;
115          try {
116            // Debugging:
117            // VM.sysWriteln("loadVMClass: trying to resolve className " + className);
118            type = RVMClassLoader.defineClassInternal(className, dataInputStream, this);
119            loaded.put(className, type);
120          } finally {
121            try {
122              // Make sure the input stream is closed.
123              dataInputStream.close();
124            } catch (IOException e) { }
125          }
126          return type;
127        } catch (NoClassDefFoundError e) {
128          throw e;
129        } catch (Throwable e) {
130          // We didn't find the class, or it wasn't valid, etc.
131          NoClassDefFoundError ncdf = new NoClassDefFoundError(className);
132          ncdf.initCause(e);
133          throw ncdf;
134        }
135      }
136    
137      public synchronized Class<?> loadClass(String className, boolean resolveClass) throws ClassNotFoundException {
138        if (!VM.runningVM) {
139          return super.loadClass(className, resolveClass);
140        }
141        if (className.startsWith("L") && className.endsWith(";")) {
142          className = className.substring(1, className.length() - 2);
143        }
144        RVMType loadedType = loaded.get(className);
145        Class<?> loadedClass;
146        if (loadedType == null) {
147          loadedClass = findClass(className);
148        } else {
149          loadedClass = loadedType.getClassForType();
150        }
151        if (resolveClass) {
152          resolveClass(loadedClass);
153        }
154        return loadedClass;
155      }
156    
157      /**
158       * Search the bootstrap class loader's classpath for given class.
159       *
160       * @param className the name of the class to load
161       * @return the class object, if it was found
162       * @exception ClassNotFoundException if the class was not found, or was invalid
163       */
164      public Class<?> findClass(String className) throws ClassNotFoundException {
165        final boolean DBG=false;
166        if (!VM.runningVM) {
167          return super.findClass(className);
168        }
169        if (className.startsWith("[")) {
170          TypeReference typeRef =
171              TypeReference.findOrCreate(this, Atom.findOrCreateAsciiAtom(className.replace('.', '/')));
172          RVMType ans = typeRef.resolve();
173          loaded.put(className, ans);
174          return ans.getClassForType();
175        } else {
176          if (!VM.fullyBooted) {
177            VM.sysWrite("Trying to load a class (");
178            VM.sysWrite(className);
179            VM.sysWrite(") too early in the booting process, before dynamic");
180            VM.sysWriteln(" class loading is enabled; aborting.");
181            VM.sysFail("Trying to load a class too early in the booting process");
182          }
183          // class types: try to find the class file
184          try {
185            if (className.startsWith("L") && className.endsWith(";")) {
186              className = className.substring(1, className.length() - 2);
187            }
188            InputStream is = getResourceAsStream(className.replace('.', File.separatorChar) + ".class");
189            if (is == null) throw new ClassNotFoundException(className);
190            DataInputStream dataInputStream = new DataInputStream(is);
191            Class<?> cls = null;
192            try {
193              RVMType type = RVMClassLoader.defineClassInternal(className, dataInputStream, this);
194              loaded.put(className, type);
195              cls = type.getClassForType();
196            } finally {
197              try {
198                // Make sure the input stream is closed.
199                dataInputStream.close();
200              } catch (IOException e) { }
201            }
202            return cls;
203          } catch (ClassNotFoundException e) {
204            throw e;
205          } catch (Throwable e) {
206            if (DBG) {
207              VM.sysWrite("About to throw ClassNotFoundException(", className, ") because we got this Throwable:");
208              e.printStackTrace();
209            }
210            // We didn't find the class, or it wasn't valid, etc.
211            throw new ClassNotFoundException(className, e);
212          }
213        }
214      }
215    
216      /** Keep this a static field, since it's looked at in
217       *  {@link MemberReference#parse}. */
218      public static final String myName = "BootstrapCL";
219    
220      public String toString() { return myName; }
221    
222      private static HashMap<String, ZipFile> zipFileCache;
223    
224      private interface Handler<T> {
225        void process(ZipFile zf, ZipEntry ze) throws Exception;
226    
227        void process(File f) throws Exception;
228    
229        T getResult();
230      }
231    
232      public InputStream getResourceAsStream(final String name) {
233        Handler<InputStream> findStream = new Handler<InputStream>() {
234          InputStream stream;
235    
236          public InputStream getResult() { return stream; }
237    
238          public void process(ZipFile zf, ZipEntry ze) throws Exception {
239            stream = zf.getInputStream(ze);
240          }
241    
242          public void process(File file) throws Exception {
243            stream = new FileInputStream(file);
244          }
245        };
246    
247        return getResourceInternal(name, findStream, false);
248      }
249    
250      public URL findResource(final String name) {
251        Handler<URL> findURL = new Handler<URL>() {
252          URL url;
253    
254          public URL getResult() { return url; }
255    
256          public void process(ZipFile zf, ZipEntry ze) throws Exception {
257            url = new URL("jar", null, -1, "file:" + zf.getName() + "!/" + name);
258          }
259    
260          public void process(File file) throws Exception {
261            url = new URL("file", null, -1, file.getName());
262          }
263        };
264    
265        return getResourceInternal(name, findURL, false);
266      }
267    
268      public Enumeration<URL> findResources(final String name) {
269        Handler<Enumeration<URL>> findURL = new Handler<Enumeration<URL>>() {
270          Vector<URL> urls;
271    
272          public Enumeration<URL> getResult() {
273            if (urls == null) urls = new Vector<URL>();
274            return urls.elements();
275          }
276    
277          public void process(ZipFile zf, ZipEntry ze) throws Exception {
278            if (urls == null) urls = new Vector<URL>();
279            urls.addElement(new URL("jar", null, -1, "file:" + zf.getName() + "!/" + name));
280          }
281    
282          public void process(File file) throws Exception {
283            if (urls == null) urls = new Vector<URL>();
284            urls.addElement(new URL("file", null, -1, file.getName()));
285          }
286        };
287    
288        return getResourceInternal(name, findURL, true);
289      }
290    
291      private <T> T getResourceInternal(String name, Handler<T> h, boolean multiple) {
292        if (name.startsWith(File.separator)) {
293          name = name.substring(File.separator.length());
294        }
295    
296        StringTokenizer tok = new StringTokenizer(getBootstrapRepositories(), File.pathSeparator);
297    
298        while (tok.hasMoreElements()) {
299          try {
300            String path = tok.nextToken();
301            if (path.endsWith(".jar") || path.endsWith(".zip")) {
302              ZipFile zf = zipFileCache.get(path);
303              if (zf == null) {
304                zf = new ZipFile(path);
305                if (zf == null) {
306                  continue;
307                } else {
308                  zipFileCache.put(path, zf);
309                }
310              }
311              // Zip spec. states that separator must be '/' in the path
312              if (File.separatorChar != '/') {
313                name = name.replace(File.separatorChar, '/');
314              }
315              ZipEntry ze = zf.getEntry(name);
316              if (ze == null) continue;
317    
318              h.process(zf, ze);
319              if (!multiple) return h.getResult();
320            } else if (path.endsWith(File.separator)) {
321              File file = new File(path + name);
322              if (file.exists()) {
323                h.process(file);
324                if (!multiple) return h.getResult();
325              }
326            } else {
327              File file = new File(path + File.separator + name);
328              if (file.exists()) {
329                h.process(file);
330                if (!multiple) return h.getResult();
331              }
332            }
333          } catch (Exception e) {
334          }
335        }
336    
337        return (multiple) ? h.getResult() : null;
338      }
339    }