package org.apache.velocity.runtime.resource.loader;

/*
 * Copyright 2001-2004 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License")
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import java.io.InputStream;

import java.util.Hashtable;
import java.util.Vector;
import java.util.Map;
import java.util.HashMap;

import org.apache.velocity.util.StringUtils;
import org.apache.velocity.runtime.resource.Resource;
import org.apache.velocity.exception.ResourceNotFoundException;
import org.apache.commons.collections.ExtendedProperties;

/**
 * <p>
 * ResourceLoader to load templates from multiple Jar files.
 * </p>
 * <p>
 * The configuration of the JarResourceLoader is straightforward -
 * You simply add the JarResourceLoader to the configuration via
 * </p>
 * <p><pre>
 *    resource.loader = jar
 *    jar.resource.loader.class = org.apache.velocity.runtime.resource.loader.JarResourceLoader
 *    jar.resource.loader.path = list of JAR &lt;URL&gt;s
 * </pre></p>
 *
 * <p> So for example, if you had a jar file on your local filesystem, you could simply do
 *    <pre>
 *    jar.resource.loader.path = jar:file:/opt/myfiles/jar1.jar
 *    </pre>
 * </p>
 * <p> Note that jar specification for the <code>.path</code> configuration property
 * conforms to the same rules for the java.net.JarUrlConnection class.
 * </p>
 *
 * <p> For a working example, see the unit test case, 
 *  org.apache.velocity.test.MultiLoaderTestCase class
 * </p>
 * 
 * @author <a href="mailto:mailmur@yahoo.com">Aki Nieminen</a>
 * @author <a href="mailto:daveb@miceda-data.com">Dave Bryson</a>
 * @version $Id$
 */
public class JarResourceLoader extends ResourceLoader
{
    /**
     * Maps entries to the parent JAR File
     * Key = the entry *excluding* plain directories
     * Value = the JAR URL
     */
    private Map entryDirectory = new HashMap(559);
    
    /**
     * Maps JAR URLs to the actual JAR
     * Key = the JAR URL
     * Value = the JAR
     */
    private Map jarfiles = new HashMap(89);
   
    /**
     * Called by Velocity to initialize the loader
     */
    public void init( ExtendedProperties configuration)
    {
        log.info("JarResourceLoader : initialization starting.");

        // rest of Velocity engine still use legacy Vector 
        // and Hashtable classes. Classes are implicitly
        // synchronized even if we don't need it.
        Vector paths = configuration.getVector("path");

        /*
         *  support the old version but deprecate with a log message
         */

        if( paths == null || paths.size() == 0)
        {
            paths = configuration.getVector("resource.path");

            if (paths != null && paths.size() > 0)
            {
                log.warn("JarResourceLoader : you are using a deprecated configuration"
                             + " property for the JarResourceLoader -> '<name>.resource.loader.resource.path'."
                             + " Please change to the conventional '<name>.resource.loader.path'.");
            }
        }
                             
        log.info("JarResourceLoader # of paths : " + paths.size() );
        
        for ( int i=0; i<paths.size(); i++ )
        {
            loadJar( (String)paths.get(i) );
        }
        
        log.info("JarResourceLoader : initialization complete.");
    }
    
    private void loadJar( String path )
    {
        log.info("JarResourceLoader : trying to load: " + path);

        // Check path information
        if ( path == null )
        {
            log.error("JarResourceLoader : can not load JAR - JAR path is null");
        }
        if ( !path.startsWith("jar:") )
        {
            log.error("JarResourceLoader : JAR path must start with jar: -> " +
                "see java.net.JarURLConnection for information");
        }
        if ( !path.endsWith("!/") )
        {
            path += "!/";
        }
        
        // Close the jar if it's already open
        // this is useful for a reload
        closeJar( path );
        
        // Create a new JarHolder
        JarHolder temp = new JarHolder( rsvc,  path );
        // Add it's entries to the entryCollection
        addEntries( temp.getEntries() );
        // Add it to the Jar table
        jarfiles.put( temp.getUrlPath(), temp );
    }

    /**
     * Closes a Jar file and set its URLConnection 
     * to null.
     */
    private void closeJar( String path )
    {
        if ( jarfiles.containsKey(path) )
        {
            JarHolder theJar = (JarHolder)jarfiles.get(path);
            theJar.close();
        }
    }
    
    /**
     * Copy all the entries into the entryDirectory
     * It will overwrite any duplicate keys.
     */
    private void addEntries( Hashtable entries )
    {
        entryDirectory.putAll( entries );
    }
    
    /**
     * Get an InputStream so that the Runtime can build a
     * template with it.
     *
     * @param name name of template to get
     * @return InputStream containing the template
     * @throws ResourceNotFoundException if template not found
     *         in the file template path.
     */
    public InputStream getResourceStream( String source )
        throws ResourceNotFoundException
    {
        InputStream results = null;

        if ( source == null || source.length() == 0)
        {
            throw new ResourceNotFoundException("Need to have a resource!");
        }
        
        String normalizedPath = StringUtils.normalizePath( source );
        
        if ( normalizedPath == null || normalizedPath.length() == 0 )
        {
            String msg = "JAR resource error : argument " + normalizedPath + 
                " contains .. and may be trying to access " + 
                "content outside of template root.  Rejected.";
            
            log.error( "JarResourceLoader : " + msg );
            
            throw new ResourceNotFoundException ( msg );
        }
        
        /*
         *  if a / leads off, then just nip that :)
         */
        if ( normalizedPath.startsWith("/") )
        {
            normalizedPath = normalizedPath.substring(1);
        }
    
        if ( entryDirectory.containsKey( normalizedPath ) )
        {
            String jarurl  = (String)entryDirectory.get( normalizedPath );
            
            if ( jarfiles.containsKey( jarurl ) )
            {
                JarHolder holder = (JarHolder)jarfiles.get( jarurl );
                results =  holder.getResource( normalizedPath );
                return results;
            }
        }
        
        throw new ResourceNotFoundException( "JarResourceLoader Error: cannot find resource " +
          source );

    }
        
        
    // TO DO BELOW 
    // SHOULD BE DELEGATED TO THE JARHOLDER
    public boolean isSourceModified(Resource resource)
    {
        return true;
    }

    public long getLastModified(Resource resource)
    {
        return 0;
    }
}

@@ -3516,32 +3516,33 @@
  %7D%0A         
+%0A
         
             
@@ -3529,27 +3529,43 @@
         
-   
+if (paths != null)%0A
         
  %0A      
@@ -3548,34 +3548,38 @@
 = null)%0A        
+%7B%0A   
  
-%0A
         log.info
@@ -3648,16 +3648,20 @@
         
+  
+  
 for ( in
@@ -3689,16 +3689,20 @@
 ; i++ )%0A
+    
         
@@ -3715,16 +3715,20 @@
         
+    
 loadJar(
@@ -3748,24 +3748,38 @@
 s.get(i) );%0A
+            %7D%0A
         %7D%0A  

Remove possible NPE for error path