001    /*******************************************************************************
002     * Copyright (C) PicoContainer Organization. All rights reserved.
003     * ---------------------------------------------------------------------------
004     * The software in this package is published under the terms of the BSD style
005     * license a copy of which has been included with this distribution in the
006     * LICENSE.txt file.
007     ******************************************************************************/
008    package org.picocontainer.script;
009    
010    import java.io.File;
011    import java.net.URL;
012    import java.util.HashMap;
013    import java.util.Map;
014    
015    /**
016     * ScriptedBuilderNameResolver handles the task of resolving a file name to a builder
017     * name. Typical default resolution is for Groovy, BeanShell, JavaScript,
018     * Jython, and XML script names. However, you can register/replace your own
019     * builder implementations by using the registerBuilder() function.
020     * 
021     * @author Michael Rimov
022     */
023    public class ScriptedBuilderNameResolver {
024    
025        public static final String GROOVY = ".groovy";
026        public static final String BEANSHELL = ".bsh";
027        public static final String JAVASCRIPT = ".js";
028        public static final String JYTHON = ".py";
029        public static final String XML = ".xml";
030    
031        public static final String DEFAULT_GROOVY_BUILDER = "org.picocontainer.script.groovy.GroovyContainerBuilder";
032        public static final String DEFAULT_BEANSHELL_BUILDER = "org.picocontainer.script.bsh.BeanShellContainerBuilder";
033        public static final String DEFAULT_JAVASCRIPT_BUILDER = "org.picocontainer.script.rhino.JavascriptContainerBuilder";
034        public static final String DEFAULT_XML_BUILDER = "org.picocontainer.script.xml.XMLContainerBuilder";
035        public static final String DEFAULT_JYTHON_BUILDER = "org.picocontainer.script.jython.JythonContainerBuilder";
036    
037        private final Map<String, String> extensionToBuilders = new HashMap<String, String>();
038    
039        public ScriptedBuilderNameResolver() {
040            resetBuilders();
041        }
042    
043        /**
044         * Returns the classname of the ScriptedContainerBuilder from the file.
045         * 
046         * @param compositionFile the composition File
047         * @return The builder class name
048         */
049        public String getBuilderClassName(File compositionFile) {
050            String language = getExtension(compositionFile.getAbsolutePath());
051            return getBuilderClassName(language);
052        }
053    
054        /**
055         * Returns the classname of the ScriptedContainerBuilder from the URL.
056         * 
057         * @param compositionURL the composition URL
058         * @return The builder class name
059         */
060        public String getBuilderClassName(URL compositionURL) {
061            String language = getExtension(compositionURL.getFile());
062            return getBuilderClassName(language);
063        }
064    
065        /**
066         * Retrieve the classname of the builder to use given the provided
067         * extension.  Example: 
068         * <pre>
069         * ScriptedContainerBuilderFactory factory = new ScriptedContainerBuilderFactory(.....);
070         * String groovyBuilderName = factory.getBuilderClassName(&quot;.groovy&quot;);
071         * assert &quot;org.picocontainer.script.groovy.GroovyContainerBuilder&quot;.equals(groovyBuilderName);
072         * </pre>
073         * 
074         * @param extension the extension 
075         * @return The builder class name
076         * @throws UnsupportedScriptTypeException
077         */
078        public synchronized String getBuilderClassName(final String extension) throws UnsupportedScriptTypeException {
079            String resultingBuilderClassName = extensionToBuilders.get(extension);
080            if (resultingBuilderClassName == null) {
081                throw new UnsupportedScriptTypeException(extension, this.getAllSupportedExtensions());
082            }
083            return resultingBuilderClassName;
084        }
085    
086        /**
087         * Function to allow the resetting of the builder map to defaults. Allows
088         * testing of the static resource a bit better.
089         */
090        public synchronized void resetBuilders() {
091            extensionToBuilders.clear();
092    
093            // This is a bit clunky compared to just registering the items
094            // directly into the map, but this way IMO it provides a single access
095            // point into the extensionToBuilders map.
096            registerBuilder(GROOVY, DEFAULT_GROOVY_BUILDER);
097            registerBuilder(BEANSHELL, DEFAULT_BEANSHELL_BUILDER);
098            registerBuilder(JAVASCRIPT, DEFAULT_JAVASCRIPT_BUILDER);
099            registerBuilder(XML, DEFAULT_XML_BUILDER);
100            registerBuilder(JYTHON, DEFAULT_JYTHON_BUILDER);
101    
102        }
103    
104        /**
105         * Registers/replaces a new handler for a given extension. Allows for
106         * customizable behavior in the various builders or the possibility to
107         * dynamically add handlers for new file types. Example: 
108         * <pre>
109         * ScriptedContainerBuilderFactory factory = new ScriptedContainerBuilderFactory(...)
110         * factory.registerBuilder(&quot;.groovy&quot;, &quot;org.picocontainer.script.groovy.GroovyContainerBuilder&quot;);
111         * ScriptedContainerBuilder builder = factory.getContainerBuilder();
112         * assertNotNull(builder);
113         * </pre>
114         * <p>
115         * The internal code now requires synchronization of the builder extension
116         * map since who knows what is using it when a new builder is registered.
117         * </p>
118         * 
119         * @param extension String the extension to register under.
120         * @param className String the classname to use for the given extension.
121         */
122        public synchronized void registerBuilder(final String extension, final String className) {
123            extensionToBuilders.put(extension, className);
124        }
125    
126        /**
127         * Returns a list of all supported extensions.
128         * 
129         * @return A String[] of extensions including the period in the name.
130         */
131        public synchronized String[] getAllSupportedExtensions() {
132            return extensionToBuilders.keySet().toArray(new String[extensionToBuilders.size()]);
133        }
134    
135        private String getExtension(String fileName) {
136            return fileName.substring(fileName.lastIndexOf("."));
137        }
138    
139    }