KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jruby > runtime > load > LoadService


1 /***** BEGIN LICENSE BLOCK *****
2  * Version: CPL 1.0/GPL 2.0/LGPL 2.1
3  *
4  * The contents of this file are subject to the Common Public
5  * License Version 1.0 (the "License"); you may not use this file
6  * except in compliance with the License. You may obtain a copy of
7  * the License at http://www.eclipse.org/legal/cpl-v10.html
8  *
9  * Software distributed under the License is distributed on an "AS
10  * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
11  * implied. See the License for the specific language governing
12  * rights and limitations under the License.
13  *
14  * Copyright (C) 2002-2004 Anders Bengtsson <ndrsbngtssn@yahoo.se>
15  * Copyright (C) 2002-2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
16  * Copyright (C) 2004 Thomas E Enebo <enebo@acm.org>
17  * Copyright (C) 2004-2005 Charles O Nutter <headius@headius.com>
18  * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
19  * Copyright (C) 2006 Ola Bini <ola@ologix.com>
20  *
21  * Alternatively, the contents of this file may be used under the terms of
22  * either of the GNU General Public License Version 2 or later (the "GPL"),
23  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
24  * in which case the provisions of the GPL or the LGPL are applicable instead
25  * of those above. If you wish to allow use of your version of this file only
26  * under the terms of either the GPL or the LGPL, and not to allow others to
27  * use your version of this file under the terms of the CPL, indicate your
28  * decision by deleting the provisions above and replace them with the notice
29  * and other provisions required by the GPL or the LGPL. If you do not delete
30  * the provisions above, a recipient may use your version of this file under
31  * the terms of any one of the CPL, the GPL or the LGPL.
32  ***** END LICENSE BLOCK *****/

33 package org.jruby.runtime.load;
34
35 import java.io.FileNotFoundException JavaDoc;
36 import java.io.IOException JavaDoc;
37 import java.net.MalformedURLException JavaDoc;
38 import java.net.URL JavaDoc;
39 import java.security.AccessControlException JavaDoc;
40 import java.util.Collections JavaDoc;
41 import java.util.HashMap JavaDoc;
42 import java.util.HashSet JavaDoc;
43 import java.util.Iterator JavaDoc;
44 import java.util.List JavaDoc;
45 import java.util.Map JavaDoc;
46 import java.util.Set JavaDoc;
47 import java.util.jar.JarFile JavaDoc;
48 import java.util.regex.Matcher JavaDoc;
49 import java.util.regex.Pattern JavaDoc;
50
51 import org.jruby.Ruby;
52 import org.jruby.RubyArray;
53 import org.jruby.exceptions.RaiseException;
54 import org.jruby.runtime.Constants;
55 import org.jruby.runtime.builtin.IRubyObject;
56 import org.jruby.util.BuiltinScript;
57 import org.jruby.util.JRubyFile;
58 import org.jruby.util.PreparsedScript;
59
60 /**
61  * <b>How require works in JRuby</b>
62  * When requiring a name from Ruby, JRuby will first remove any file extension it knows about,
63  * thereby making it possible to use this string to see if JRuby has already loaded
64  * the name in question. If a .rb or .rb.ast.ser extension is specified, JRuby will only try
65  * those extensions when searching. If a .so, .o, .dll, or .jar extension is specified, JRuby
66  * will only try .so or .jar when searching. Otherwise, JRuby goes through the known suffixes
67  * (.rb, .rb.ast.ser, .so, and .jar) and tries to find a library with this name. The process for finding a library follows this order
68  * for all searchable extensions:
69  * <ol>
70  * <li>First, check if the name starts with 'jar:', then the path points to a jar-file resource which is returned.</li>
71  * <li>Second, try searching for the file in the current dir</li>
72  * <li>Then JRuby looks through the load path trying these variants:
73  * <ol>
74  * <li>See if the current load path entry starts with 'jar:', if so check if this jar-file contains the name</li>
75  * <li>Otherwise JRuby tries to construct a path by combining the entry and the current working directy, and then see if
76  * a file with the correct name can be reached from this point.</li>
77  * </ol>
78  * </li>
79  * <li>If all these fail, try to load the name as a resource from classloader resources, using the bare name as
80  * well as the load path entries</li>
81  * <li>When we get to this state, the normal JRuby loading has failed. At this stage JRuby tries to load
82  * Java native extensions, by following this process:
83  * <ol>
84  * <li>First it checks that we haven't already found a library. If we found a library of type JarredScript, the method continues.</li>
85  * <li>The first step is translating the name given into a valid Java Extension class name. First it splits the string into
86  * each path segment, and then makes all but the last downcased. After this it takes the last entry, removes all underscores
87  * and capitalizes each part separated by underscores. It then joins everything together and tacks on a 'Service' at the end.
88  * Lastly, it removes all leading dots, to make it a valid Java FWCN.</li>
89  * <li>If the previous library was of type JarredScript, we try to add the jar-file to the classpath</li>
90  * <li>Now JRuby tries to instantiate the class with the name constructed. If this works, we return a ClassExtensionLibrary. Otherwise,
91  * the old library is put back in place, if there was one.
92  * </ol>
93  * </li>
94  * <li>When all separate methods have been tried and there was no result, a LoadError will be raised.</li>
95  * <li>Otherwise, the name will be added to the loaded features, and the library loaded</li>
96  * </ol>
97  *
98  * <b>How to make a class that can get required by JRuby</b>
99  * <p>First, decide on what name should be used to require the extension.
100  * In this purely hypothetical example, this name will be 'active_record/connection_adapters/jdbc_adapter'.
101  * Then create the class name for this require-name, by looking at the guidelines above. Our class should
102  * be named active_record.connection_adapters.JdbcAdapterService, and implement one of the library-interfaces.
103  * The easiest one is BasicLibraryService, where you define the basicLoad-method, which will get called
104  * when your library should be loaded.</p>
105  * <p>The next step is to either put your compiled class on JRuby's classpath, or package the class/es inside a
106  * jar-file. To package into a jar-file, we first create the file, then rename it to jdbc_adapter.jar. Then
107  * we put this jar-file in the directory active_record/connection_adapters somewhere in JRuby's load path. For
108  * example, copying jdbc_adapter.jar into JRUBY_HOME/lib/ruby/site_ruby/1.8/active_record/connection_adapters
109  * will make everything work. If you've packaged your extension inside a RubyGem, write a setub.rb-script that
110  * copies the jar-file to this place.</p>
111  * <p>If you don't want to have the name of your extension-class to be prescribed, you can also put a file called
112  * jruby-ext.properties in your jar-files META-INF directory, where you can use the key <full-extension-name>.impl
113  * to make the extension library load the correct class. An example for the above would have a jruby-ext.properties
114  * that contained a ruby like: "active_record/connection_adapters/jdbc_adapter=org.jruby.ar.JdbcAdapter". (NOTE: THIS
115  * FEATURE IS NOT IMPLEMENTED YET.)</p>
116  *
117  * @author jpetersen
118  */

119 public class LoadService {
120     private static final String JavaDoc JRUBY_BUILTIN_SUFFIX = ".rb";
121
122     private static final String JavaDoc[] sourceSuffixes = { ".rb", ".rb.ast.ser" };
123     private static final String JavaDoc[] extensionSuffixes = { ".so", ".jar" };
124     private static final String JavaDoc[] allSuffixes = { ".rb", ".rb.ast.ser", ".so", ".jar" };
125     private static final Pattern JavaDoc sourcePattern = Pattern.compile("^(.*)\\.(rb|rb\\.ast\\.ser)$");
126     private static final Pattern JavaDoc extensionPattern = Pattern.compile("^(.*)\\.(so|o|dll|jar)$");
127
128     private final RubyArray loadPath;
129     private final RubyArray loadedFeatures;
130     private final Set JavaDoc loadedFeaturesInternal = Collections.synchronizedSet(new HashSet JavaDoc());
131     private final Set JavaDoc firstLineLoadedFeatures = Collections.synchronizedSet(new HashSet JavaDoc());
132     private final Map JavaDoc builtinLibraries = new HashMap JavaDoc();
133
134     private final Map JavaDoc jarFiles = new HashMap JavaDoc();
135
136     private final Map JavaDoc autoloadMap = new HashMap JavaDoc();
137
138     private final Ruby runtime;
139     
140     public LoadService(Ruby runtime) {
141         this.runtime = runtime;
142         loadPath = RubyArray.newArray(runtime);
143         loadedFeatures = RubyArray.newArray(runtime);
144     }
145
146     public void init(List JavaDoc additionalDirectories) {
147         // add all startup load paths to the list first
148
for (Iterator JavaDoc iter = additionalDirectories.iterator(); iter.hasNext();) {
149             addPath((String JavaDoc) iter.next());
150         }
151
152         // wrap in try/catch for security exceptions in an applet
153
try {
154           String JavaDoc jrubyHome = runtime.getJRubyHome();
155           if (jrubyHome != null) {
156               char sep = '/';
157               String JavaDoc rubyDir = jrubyHome + sep + "lib" + sep + "ruby" + sep;
158
159               addPath(rubyDir + "site_ruby" + sep + Constants.RUBY_MAJOR_VERSION);
160               addPath(rubyDir + "site_ruby");
161               addPath(rubyDir + Constants.RUBY_MAJOR_VERSION);
162               addPath(rubyDir + Constants.RUBY_MAJOR_VERSION + sep + "java");
163
164               // Added to make sure we find default distribution files within jar file.
165
// TODO: Either make jrubyHome become the jar file or allow "classpath-only" paths
166
addPath("lib" + sep + "ruby" + sep + Constants.RUBY_MAJOR_VERSION);
167           }
168         } catch (AccessControlException JavaDoc accessEx) {
169           // ignore, we're in an applet and can't access filesystem anyway
170
}
171         
172         // "." dir is used for relative path loads from a given file, as in require '../foo/bar'
173
if (runtime.getSafeLevel() == 0) {
174             addPath(".");
175         }
176     }
177
178     private void addPath(String JavaDoc path) {
179         synchronized(loadPath) {
180             loadPath.add(runtime.newString(path.replace('\\', '/')));
181         }
182     }
183
184     public void load(String JavaDoc file) {
185         if(!runtime.getProfile().allowLoad(file)) {
186             throw runtime.newLoadError("No such file to load -- " + file);
187         }
188
189         Library library = null;
190         
191         library = findLibrary(file);
192
193         if (library == null) {
194             library = findLibraryWithClassloaders(file);
195             if (library == null) {
196                 throw runtime.newLoadError("No such file to load -- " + file);
197             }
198         }
199         try {
200             library.load(runtime);
201         } catch (IOException JavaDoc e) {
202             throw runtime.newLoadError("IO error -- " + file);
203         }
204     }
205
206     public boolean smartLoad(String JavaDoc file) {
207         if(firstLineLoadedFeatures.contains(file)) {
208             return false;
209         }
210         Library library = null;
211         String JavaDoc loadName = file;
212         String JavaDoc[] extensionsToSearch = null;
213         
214         // if an extension is specified, try more targetted searches
215
if (file.lastIndexOf('.') > file.lastIndexOf('/')) {
216             Matcher JavaDoc matcher = null;
217             if ((matcher = sourcePattern.matcher(file)).matches()) {
218                 // source extensions
219
extensionsToSearch = sourceSuffixes;
220                 
221                 // trim extension to try other options
222
file = matcher.group(1);
223             } else if ((matcher = extensionPattern.matcher(file)).matches()) {
224                 // extension extensions
225
extensionsToSearch = extensionSuffixes;
226                 
227                 // trim extension to try other options
228
file = matcher.group(1);
229             } else {
230                 // unknown extension
231
throw runtime.newLoadError("no such file to load -- " + file);
232             }
233         } else {
234             // try all extensions
235
extensionsToSearch = allSuffixes;
236         }
237         
238         // First try suffixes with normal loading
239
for (int i = 0; i < extensionsToSearch.length; i++) {
240             library = findLibrary(file + extensionsToSearch[i]);
241             if (library != null) {
242                 loadName = file + extensionsToSearch[i];
243                 break;
244             }
245         }
246
247         // Then try suffixes with classloader loading
248
if (library == null) {
249             for (int i = 0; i < extensionsToSearch.length; i++) {
250                 library = findLibraryWithClassloaders(file + extensionsToSearch[i]);
251                 if (library != null) {
252                     loadName = file + extensionsToSearch[i];
253                     break;
254                 }
255             }
256         }
257
258         library = tryLoadExtension(library,file);
259
260         // no library or extension found, bail out
261
if (library == null) {
262             throw runtime.newLoadError("no such file to load -- " + file);
263         }
264         
265         if (loadedFeaturesInternal.contains(loadName)) {
266             return false;
267         }
268         
269         // attempt to load the found library
270
try {
271             loadedFeaturesInternal.add(loadName);
272             firstLineLoadedFeatures.add(file);
273             synchronized(loadedFeatures) {
274                 loadedFeatures.add(runtime.newString(loadName));
275             }
276
277             library.load(runtime);
278             return true;
279         } catch (Exception JavaDoc e) {
280             loadedFeaturesInternal.remove(loadName);
281             firstLineLoadedFeatures.remove(file);
282             synchronized(loadedFeatures) {
283                 loadedFeatures.remove(runtime.newString(loadName));
284             }
285             if (e instanceof RaiseException) throw (RaiseException) e;
286
287             if(runtime.getDebug().isTrue()) e.printStackTrace();
288
289             RaiseException re = runtime.newLoadError("IO error -- " + file);
290             re.initCause(e);
291             throw re;
292         }
293     }
294
295     public boolean require(String JavaDoc file) {
296         if(!runtime.getProfile().allowRequire(file)) {
297             throw runtime.newLoadError("No such file to load -- " + file);
298         }
299         return smartLoad(file);
300     }
301
302     public IRubyObject getLoadPath() {
303         return loadPath;
304     }
305
306     public IRubyObject getLoadedFeatures() {
307         return loadedFeatures;
308     }
309
310     public boolean isAutoloadDefined(String JavaDoc name) {
311         return autoloadMap.containsKey(name);
312     }
313
314     public IAutoloadMethod autoloadFor(String JavaDoc name) {
315         return (IAutoloadMethod)autoloadMap.get(name);
316     }
317
318     public IRubyObject autoload(String JavaDoc name) {
319         IAutoloadMethod loadMethod = (IAutoloadMethod)autoloadMap.remove(name);
320         if (loadMethod != null) {
321             return loadMethod.load(runtime, name);
322         }
323         return null;
324     }
325
326     public void addAutoload(String JavaDoc name, IAutoloadMethod loadMethod) {
327         autoloadMap.put(name, loadMethod);
328     }
329
330     public void registerBuiltin(String JavaDoc name, Library library) {
331         builtinLibraries.put(name, library);
332     }
333
334     public void registerRubyBuiltin(String JavaDoc libraryName) {
335         registerBuiltin(libraryName + JRUBY_BUILTIN_SUFFIX, new BuiltinScript(libraryName));
336     }
337
338     private Library findLibrary(String JavaDoc file) {
339         if (builtinLibraries.containsKey(file)) {
340             return (Library) builtinLibraries.get(file);
341         }
342         
343         LoadServiceResource resource = findFile(file);
344         if (resource == null) {
345             return null;
346         }
347
348         if (file.endsWith(".jar")) {
349             return new JarredScript(resource);
350         } else if (file.endsWith(".rb.ast.ser")) {
351             return new PreparsedScript(resource);
352         } else {
353             return new ExternalScript(resource, file);
354         }
355     }
356
357     private Library findLibraryWithClassloaders(String JavaDoc file) {
358         LoadServiceResource resource = findFileInClasspath(file);
359         if (resource == null) {
360             return null;
361         }
362
363         if (file.endsWith(".jar")) {
364             return new JarredScript(resource);
365         } else if (file.endsWith(".rb.ast.ser")) {
366             return new PreparsedScript(resource);
367         } else {
368             return new ExternalScript(resource, file);
369         }
370     }
371
372     /**
373      * this method uses the appropriate lookup strategy to find a file.
374      * It is used by Kernel#require.
375      *
376      * @mri rb_find_file
377      * @param name the file to find, this is a path name
378      * @return the correct file
379      */

380     private LoadServiceResource findFile(String JavaDoc name) {
381         // if a jar URL, return load service resource directly without further searching
382
if (name.startsWith("jar:")) {
383             try {
384                 return new LoadServiceResource(new URL JavaDoc(name), name);
385             } catch (MalformedURLException JavaDoc e) {
386                 throw runtime.newIOErrorFromException(e);
387             }
388         }
389
390         // check current directory; if file exists, retrieve URL and return resource
391
try {
392             JRubyFile file = JRubyFile.create(runtime.getCurrentDirectory(),name);
393             if(file.isFile() && file.isAbsolute()) {
394                 try {
395                     return new LoadServiceResource(file.toURL(),name);
396                 } catch (MalformedURLException JavaDoc e) {
397                     throw runtime.newIOErrorFromException(e);
398                 }
399             }
400         } catch (AccessControlException JavaDoc accessEx) {
401             // ignore, applet security
402
} catch (IllegalArgumentException JavaDoc illArgEx) {
403         }
404
405         for (Iterator JavaDoc pathIter = loadPath.getList().iterator(); pathIter.hasNext();) {
406             String JavaDoc entry = pathIter.next().toString();
407             if (entry.startsWith("jar:")) {
408                 JarFile JavaDoc current = (JarFile JavaDoc)jarFiles.get(entry);
409                 if(null == current) {
410                     try {
411                         current = new JarFile JavaDoc(entry.substring(4));
412                         jarFiles.put(entry,current);
413                     } catch (FileNotFoundException JavaDoc ignored) {
414                     } catch (IOException JavaDoc e) {
415                         throw runtime.newIOErrorFromException(e);
416                     }
417                 }
418                 if (current.getJarEntry(name) != null) {
419                     try {
420                         return new LoadServiceResource(new URL JavaDoc(entry + name), entry + name);
421                     } catch (MalformedURLException JavaDoc e) {
422                         throw runtime.newIOErrorFromException(e);
423                     }
424                 }
425             }
426
427             try {
428                 JRubyFile current = JRubyFile.create(JRubyFile.create(runtime.getCurrentDirectory(),entry).getAbsolutePath(), name);
429                 if (current.isFile()) {
430                     try {
431                         return new LoadServiceResource(current.toURL(), current.getPath());
432                     } catch (MalformedURLException JavaDoc e) {
433                         throw runtime.newIOErrorFromException(e);
434                     }
435                 }
436             } catch (AccessControlException JavaDoc accessEx) {
437                 // ignore, we're in an applet
438
} catch (IllegalArgumentException JavaDoc illArgEx) {
439                 // ignore; Applet under windows has issues with current dir = "/"
440
}
441         }
442
443         return null;
444     }
445
446     /**
447      * this method uses the appropriate lookup strategy to find a file.
448      * It is used by Kernel#require.
449      *
450      * @mri rb_find_file
451      * @param name the file to find, this is a path name
452      * @return the correct file
453      */

454     private LoadServiceResource findFileInClasspath(String JavaDoc name) {
455         // Look in classpath next (we do not use File as a test since UNC names will match)
456
// Note: Jar resources must NEVER begin with an '/'. (previous code said "always begin with a /")
457
ClassLoader JavaDoc classLoader = Thread.currentThread().getContextClassLoader();
458
459         for (Iterator JavaDoc pathIter = loadPath.getList().iterator(); pathIter.hasNext();) {
460             String JavaDoc entry = pathIter.next().toString();
461
462             // if entry starts with a slash, skip it since classloader resources never start with a /
463
if (entry.charAt(0) == '/' || (entry.length() > 1 && entry.charAt(1) == ':')) continue;
464             
465             // otherwise, try to load from classpath (Note: Jar resources always uses '/')
466
URL JavaDoc loc = classLoader.getResource(entry + "/" + name);
467
468             // Make sure this is not a directory or unavailable in some way
469
if (isRequireable(loc)) {
470                 return new LoadServiceResource(loc, loc.getPath());
471             }
472         }
473
474         // if name starts with a / we're done (classloader resources won't load with an initial /)
475
if (name.charAt(0) == '/' || (name.length() > 1 && name.charAt(1) == ':')) return null;
476         
477         // Try to load from classpath without prefix. "A/b.rb" will not load as
478
// "./A/b.rb" in a jar file.
479
URL JavaDoc loc = classLoader.getResource(name);
480
481         return isRequireable(loc) ? new LoadServiceResource(loc, loc.getPath()) : null;
482     }
483
484     private Library tryLoadExtension(Library library, String JavaDoc file) {
485         // This code exploits the fact that all .jar files will be found for the JarredScript feature.
486
// This is where the basic extension mechanism gets fixed
487
Library oldLibrary = library;
488         
489         if(library == null || library instanceof JarredScript) {
490             // Create package name, by splitting on / and joining all but the last elements with a ".", and downcasing them.
491
String JavaDoc[] all = file.split("/");
492             StringBuffer JavaDoc finName = new StringBuffer JavaDoc();
493             for(int i=0, j=(all.length-1); i<j; i++) {
494                 finName.append(all[i].toLowerCase()).append(".");
495                 
496             }
497             
498             // Make the class name look nice, by splitting on _ and capitalize each segment, then joining
499
// the, together without anything separating them, and last put on "Service" at the end.
500
String JavaDoc[] last = all[all.length-1].split("_");
501             for(int i=0, j=last.length; i<j; i++) {
502                 finName.append(Character.toUpperCase(last[i].charAt(0))).append(last[i].substring(1));
503             }
504             finName.append("Service");
505
506             // We don't want a package name beginning with dots, so we remove them
507
String JavaDoc className = finName.toString().replaceAll("^\\.*","");
508
509             // If there is a jar-file with the required name, we add this to the class path.
510
if(library instanceof JarredScript) {
511                 // It's _really_ expensive to check that the class actually exists in the Jar, so
512
// we don't do that now.
513
runtime.getJavaSupport().addToClasspath(((JarredScript)library).getResource().getURL());
514             }
515
516             try {
517                 Class JavaDoc theClass = runtime.getJavaSupport().loadJavaClass(className);
518                 library = new ClassExtensionLibrary(theClass);
519             } catch(Exception JavaDoc ee) {
520                 library = null;
521             }
522         }
523         
524         // If there was a good library before, we go back to that
525
if(library == null && oldLibrary != null) {
526             library = oldLibrary;
527         }
528         return library;
529     }
530     
531     /* Directories and unavailable resources are not able to open a stream. */
532     private boolean isRequireable(URL JavaDoc loc) {
533         if (loc != null) {
534             if (loc.getProtocol().equals("file") && new java.io.File JavaDoc(loc.getFile()).isDirectory()) {
535                 return false;
536             }
537             
538             try {
539                 loc.openConnection();
540                 return true;
541             } catch (Exception JavaDoc e) {}
542         }
543         return false;
544     }
545 }
546
Popular Tags