001    /*****************************************************************************
002     * Copyright (C) PicoContainer Organization. All rights reserved.            *
003     * ------------------------------------------------------------------------- *
004     * The software in this package is published under the terms of the BSD      *
005     * style license a copy of which has been included with this distribution in *
006     * the LICENSE.txt file.                                                     *
007     * Original code by Nick Sieger                                                                          *
008     *****************************************************************************/
009    
010    package org.picocontainer.script.jruby;
011    
012    import java.io.IOException;
013    import java.io.Reader;
014    import java.io.StringWriter;
015    import java.util.Collections;
016    
017    import org.jruby.Ruby;
018    import org.jruby.RubyInstanceConfig;
019    import org.jruby.exceptions.RaiseException;
020    import org.jruby.javasupport.JavaEmbedUtils;
021    import org.jruby.runtime.builtin.IRubyObject;
022    import org.picocontainer.PicoCompositionException;
023    import org.picocontainer.PicoContainer;
024    import org.picocontainer.DefaultPicoContainer;
025    import org.picocontainer.containers.EmptyPicoContainer;
026    import org.picocontainer.classname.DefaultClassLoadingPicoContainer;
027    import org.picocontainer.script.LifecycleMode;
028    import org.picocontainer.script.ScriptedPicoContainerMarkupException;
029    import org.picocontainer.script.ScriptedContainerBuilder;
030    import org.picocontainer.behaviors.Caching;
031    
032    /**
033     * The script uses the {@code scriptedcontainer.rb} script to create an instance of
034     * {@link PicoContainer}. There are implicit variables named "$parent" and
035     * "$assembly_scope".
036     * 
037     * @author Nick Sieger
038     */
039    public final class JRubyContainerBuilder extends ScriptedContainerBuilder {
040            public static final String MARKUP_EXCEPTION_PREFIX = "scriptedbuilder: ";
041    
042            private final String script;
043    
044            public JRubyContainerBuilder(Reader script, ClassLoader classLoader) {
045                    this(script,classLoader, LifecycleMode.AUTO_LIFECYCLE);
046            }
047            
048            
049            public JRubyContainerBuilder(Reader script, ClassLoader classLoader, LifecycleMode lifecycle) {
050                    super(script, classLoader, lifecycle);
051                    this.script = toString(script);
052            }
053    
054            private String toString(Reader script) {
055                    int charsRead;
056                    char[] chars = new char[1024];
057                    StringWriter writer = new StringWriter();
058                    try {
059                            while ((charsRead = script.read(chars)) != -1) {
060                                    writer.write(chars, 0, charsRead);
061                            }
062                    } catch (IOException e) {
063                            throw new RuntimeException("unable to read script from reader", e);
064                    }
065                    return writer.toString();
066            }
067    
068            /**
069             * {@inheritDoc}
070             * <p>Latest method of invoking jruby script have been adapted from <a 
071             * href="http://wiki.jruby.org/wiki/Java_Integration" title="Click to visit JRuby Wiki">
072             * JRuby wiki:</a></p>
073             * @todo create a way to prevent initialization and shutdown with each script invocation.
074             */
075            protected PicoContainer createContainerFromScript(PicoContainer parentContainer, Object assemblyScope) {
076                    if (parentContainer == null) {
077                            parentContainer = new EmptyPicoContainer();
078                    }
079                    parentContainer = new DefaultClassLoadingPicoContainer(getClassLoader(), new DefaultPicoContainer(new Caching(),
080                            parentContainer));
081    
082                    
083                    
084                    RubyInstanceConfig rubyConfig = new RubyInstanceConfig();
085                    rubyConfig.setLoader(this.getClassLoader());
086                    Ruby ruby = JavaEmbedUtils.initialize(Collections.EMPTY_LIST, rubyConfig);
087                    ruby.getLoadService().require("org/picocontainer/script/jruby/scriptedbuilder");
088                    ruby.defineReadonlyVariable("$parent", JavaEmbedUtils.javaToRuby(ruby, parentContainer));
089                    ruby.defineReadonlyVariable("$assembly_scope", JavaEmbedUtils.javaToRuby(ruby, assemblyScope));
090                    
091                    
092                    try {
093                            
094                            //IRubyObject result = ruby.executeScript(script);
095                            IRubyObject result = JavaEmbedUtils.newRuntimeAdapter().eval(ruby, script);
096                            return (PicoContainer) JavaEmbedUtils.rubyToJava(ruby, result, PicoContainer.class);
097                    } catch (RaiseException re) {
098                            if (re.getCause() instanceof ScriptedPicoContainerMarkupException) {
099                                    throw (ScriptedPicoContainerMarkupException) re.getCause();
100                            }
101                            String message = (String) JavaEmbedUtils.rubyToJava(ruby, re.getException().message, String.class);
102                            if (message.startsWith(MARKUP_EXCEPTION_PREFIX)) {
103                                    throw new ScriptedPicoContainerMarkupException(message.substring(MARKUP_EXCEPTION_PREFIX.length()));
104                            } else {
105                                    throw new PicoCompositionException(message, re);
106                            }
107                    } finally {
108                            JavaEmbedUtils.terminate(ruby);
109                    }
110            }
111    }