//========================================================================
//$Id: AndroidClassLoader.java 391 2011-02-08 01:06:04Z janb.webtide $
//Copyright 2008 Mort Bay Consulting Pty. Ltd.
//------------------------------------------------------------------------
//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.
//========================================================================

package org.mortbay.ijetty.webapp;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.StringTokenizer;

import org.eclipse.jetty.webapp.WebAppClassLoader;
import org.eclipse.jetty.webapp.WebAppContext;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.resource.ResourceCollection;

import dalvik.system.DexClassLoader;


/**
 * AndroidClassLoader
 * 
 * Loads classes dynamically from dex files wrapped inside a zip.
 */
@SuppressWarnings("unchecked")
public class AndroidClassLoader extends ClassLoader //extends WebAppClassLoader
{
    private WebAppContext _context;
    private ClassLoader _parent;
    private ClassLoader _delegate;
    private String _path = "";


    public AndroidClassLoader(String path, ClassLoader parent, WebAppContext context) throws IOException
    {
        this(parent, context);
        _path = path;
    }

    public AndroidClassLoader(WebAppContext context)
    throws IOException
    {
        this (ClassLoader.getSystemClassLoader(), context);
    }
    
    public AndroidClassLoader(ClassLoader parent, WebAppContext context)
    throws IOException
    {
        //super(parent, context);
        _parent = parent;
        _context = context;
    }

    public WebAppContext getContext ()
    {
        return _context;
    }

    public void init ()
    throws IOException
    {
        if (_path==null || "".equals(_path.trim()))
            _delegate = new DexClassLoader("", ((WebAppContext)getContext()).getTempDirectory().getCanonicalPath(),null,_parent);
        else
            _delegate = new DexClassLoader(_path, ((WebAppContext)getContext()).getTempDirectory().getCanonicalPath(), null, _parent);
        
        if (Log.isDebugEnabled()) Log.debug("Android webapp classloader path= "+_path+" tmpdir="+ ((WebAppContext)getContext()).getTempDirectory()+" dexloader = "+_delegate+" parentloader="+_parent);
    }



    public void addClassPath(Resource resource)
    throws IOException
    {
        if (resource instanceof ResourceCollection)
        {
            for (Resource r : ((ResourceCollection)resource).getResources())
                addClassPath(r);
        }
        else
        {
            addClassPath(resource.getFile().getAbsolutePath());
        }
    }

   


    /** 
     * Accept a pre-made classpath. 
     * NOTE: the path elements must be separated by ":" chars, not ";"
     * @see org.eclipse.jetty.webapp.WebAppClassLoader#addClassPath(java.lang.String)
     */
    public void addClassPath(String classPath) throws IOException
    {
        if (classPath == null)
            return;
        
        if (!"".equals(_path) && !_path.endsWith(":"))
            _path += ":";
        
        _path += classPath; 
        Log.debug("Path = "+_path);
    }


    public void addJars(Resource lib)
    {
        if (lib.exists() && lib.isDirectory())
        {
            String[] files=lib.list();
            for (int f=0;files!=null && f<files.length;f++)
            {
                try 
                {
                    Resource fn=lib.addPath(files[f]);
                    String fnlc=fn.getName().toLowerCase();
                    
                    if (!fn.isDirectory() && isAndroidArchive(fnlc))
                    {
                        String jar=fn.getFile().getAbsolutePath();
                        addClassPath(jar);
                    }
                }
                catch (Exception ex)
                {
                    Log.warn(Log.EXCEPTION,ex);
                }
            }
        }
    }
    
    
    
    protected boolean isAndroidArchive (String filename)
    {
        int dot = filename.lastIndexOf('.');
        if (dot == -1)
            return false;
        
        String extension = filename.substring(dot);
        return ".zip".equals(extension) || ".apk".equals(extension);
    }
    
    public Enumeration<URL> getResources(String name) throws IOException
    {
        boolean system_class=_context.isSystemClass(name);
        boolean server_class=_context.isServerClass(name);
        
        List<URL> from_parent = toList(server_class?null:_parent.getResources(name));
        List<URL> from_webapp = toList((system_class&&!from_parent.isEmpty())?null:this.findResources(name));
            
        if (_context.isParentLoaderPriority())
        {
            from_parent.addAll(from_webapp);
            return Collections.enumeration(from_parent);
        }
        from_webapp.addAll(from_parent);
        return Collections.enumeration(from_webapp);
    }


    private List<URL> toList(Enumeration<URL> e)
    {
        List<URL> list = new ArrayList<URL>();
        while (e!=null && e.hasMoreElements())
            list.add(e.nextElement());
        return list;
    }
    

    public URL getResource(String name)
    {
        URL url= null;
        boolean tried_parent= false;
        boolean system_class=_context.isSystemClass(name);
        boolean server_class=_context.isServerClass(name);
        
        if (system_class && server_class)
            return null;
        
        if (_parent!=null &&(_context.isParentLoaderPriority() || system_class ) && !server_class)
        {
            tried_parent= true;
            
            if (_parent!=null)
                url= _parent.getResource(name);
        }

        if (url == null)
        {
            url= this.findResource(name);

            if (url == null && name.startsWith("/"))
            {
                if (Log.isDebugEnabled())
                    Log.debug("HACK leading / off " + name);
                url= this.findResource(name.substring(1));
            }
        }

        if (url == null && !tried_parent && !server_class )
        {
            if (_parent!=null)
                url= _parent.getResource(name);
        }

        if (url != null)
            if (Log.isDebugEnabled())
                Log.debug("getResource("+name+")=" + url);

        return url;
    }

    protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException
    {
        Class<?> c= findLoadedClass(name);
        ClassNotFoundException ex= null;
        boolean tried_parent= false;
        
        boolean system_class=((WebAppContext)getContext()).isSystemClass(name);
        boolean server_class=((WebAppContext)getContext()).isServerClass(name);
        
        if (system_class && server_class)
        {
            return null;
        }
        
        if (c == null && _parent!=null && (((WebAppContext)getContext()).isParentLoaderPriority() || system_class) && !server_class)
        {
            tried_parent= true;
            try
            {
                c= _parent.loadClass(name);
                if (Log.isDebugEnabled())
                    Log.debug("loaded " + c);
            }
            catch (ClassNotFoundException e)
            {
                ex= e;
            }
        }

        if (c == null)
        {
            try
            {
                if (_delegate != null)
                {
                    if (Log.isDebugEnabled()) Log.debug("loading class "+name+" trying delegate loader" +_delegate);
                    c = _delegate.loadClass(name);
                    if (Log.isDebugEnabled()) Log.debug("delegate loaded " + c);
                }
            }
            catch (ClassNotFoundException e)
            {
                ex= e;
            }
        }
        
        
        if (c == null && _parent!=null && !tried_parent && !server_class )
            c= _parent.loadClass(name);

        if (c == null)
            throw ex;

        if (resolve)
            resolveClass(c);

        if (Log.isDebugEnabled())
            Log.debug("loaded " + c+ " from "+c.getClassLoader());
        
        return c;
    }


    @Override
    public String toString()
    {
    	return "(AndroidClassLoader, delegate=" + _delegate + ")";
    }
}
