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