/**************************************************************
 * 
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 com.sun.star.comp.loader;

import java.lang.reflect.Method;

import java.lang.reflect.InvocationTargetException;

import java.net.URLDecoder;

import com.sun.star.loader.CannotActivateFactoryException;
import com.sun.star.loader.XImplementationLoader;

import com.sun.star.registry.CannotRegisterImplementationException;
import com.sun.star.registry.XRegistryKey;

import com.sun.star.lang.XSingleComponentFactory;
import com.sun.star.lang.XSingleServiceFactory;
import com.sun.star.lang.XMultiServiceFactory;
import com.sun.star.lang.XServiceInfo;
import com.sun.star.lang.XInitialization;

import com.sun.star.uno.XComponentContext;
import com.sun.star.beans.XPropertySet;
import com.sun.star.util.XMacroExpander;

import com.sun.star.uno.Type;
import com.sun.star.uno.UnoRuntime;

import com.sun.star.lib.util.StringHelper;

import com.sun.star.uno.AnyConverter;


/**
 * The <code>JavaLoader</code> class provides the functionality of the <code>com.sun.star.loader.Java</code>
 * service. Therefor the <code>JavaLoader</code> activates external UNO components which are implemented in Java.
 * The loader is used by the <code>ServiceManger</code>.
 * <p>
 * @version 	$Revision: 1.16 $ $ $Date: 2008-04-11 11:10:31 $
 * @author 	    Markus Herzog
 * @see         com.sun.star.loader.XImplementationLoader
 * @see         com.sun.star.loader.Java
 * @see         com.sun.star.comp.servicemanager.ServiceManager
 * @see			com.sun.star.lang.ServiceManager
 * @since       UDK1.0
 */									   
public class JavaLoader implements XImplementationLoader, 
                                   XServiceInfo,
                                   XInitialization
{
	private static final boolean DEBUG = false;
	
	private static final void DEBUG(String dbg) {
	    if (DEBUG) System.err.println( dbg );
	}
	
    private static String[] supportedServices = {
        "com.sun.star.loader.Java"
    };
    	
	protected XMultiServiceFactory multiServiceFactory = null;
    
	private XMacroExpander m_xMacroExpander = null;
    private static final String EXPAND_PROTOCOL_PREFIX = "vnd.sun.star.expand:";

    /** Expands macrofied url using the macro expander singleton.
     */
    private String expand_url( String url ) throws RuntimeException
    {
        if (url != null && url.startsWith( EXPAND_PROTOCOL_PREFIX ))
        {
            try
            {
                if (m_xMacroExpander == null)
                {
                    XPropertySet xProps =
                        UnoRuntime.queryInterface(
                            XPropertySet.class, multiServiceFactory );
                    if (xProps == null)
                    {
                        throw new com.sun.star.uno.RuntimeException(
                            "service manager does not support XPropertySet!",
                            this );
                    }
                    XComponentContext xContext = (XComponentContext)
                        AnyConverter.toObject(
                            new Type( XComponentContext.class ),
                            xProps.getPropertyValue( "DefaultContext" ) );
                    m_xMacroExpander = (XMacroExpander)AnyConverter.toObject(
                        new Type( XMacroExpander.class ),
                        xContext.getValueByName(
                            "/singletons/com.sun.star.util.theMacroExpander" )
                        );
                }
                // decode uric class chars
                String macro = URLDecoder.decode(
                    StringHelper.replace(
                        url.substring( EXPAND_PROTOCOL_PREFIX.length() ),
                        '+', "%2B" ) );
                // expand macro string
                String ret = m_xMacroExpander.expandMacros( macro );
                if (DEBUG)
                {
                    System.err.println(
                        "JavaLoader.expand_url(): " + url + " => " +
                        macro + " => " + ret );
                }
                return ret;
            }
            catch (com.sun.star.uno.Exception exc)
            {
                throw new com.sun.star.uno.RuntimeException(
                    exc.getMessage(), this );
            }
            catch (java.lang.Exception exc)
            {
                throw new com.sun.star.uno.RuntimeException(
                    exc.getMessage(), this );
            }
        }
        return url;
    }
    
	/** default constructor
	 */
	 
	/**
	 * Creates a new instance of the <code>JavaLoader</code> class.
	 * <p>
	 * @return	new instance    
	 */
	public JavaLoader() {}
	
	/**
	 * Creates a new <code>JavaLoader</code> object. The specified <code>com.sun.star.lang.XMultiServiceFactory</code>
	 * is the <code>ServiceManager</code> service which can be deliviert to all components the <code>JavaLoader</code> is
	 * loading. 
	 * To set the <code>MultiServiceFactory</code> you can use the <code>com.sun.star.lang.XInitialization</code> interface, either.
	 * <p>
	 * @return	new instance    
	 * @param	factory		the <code>ServiceManager</code>      
	 * @see		com.sun.star.lang.ServiceManager
	 * @see		com.sun.star.lang.ServiceManager	 
	 * @see		com.sun.star.lang.XInitialization
	 */
	public JavaLoader(XMultiServiceFactory factory) {
	    multiServiceFactory = factory;
	}

	/**
     * Unlike the original intention, the method could be called every time a new  
     * <code>com.sun.star.lang.XMultiServiceFactory</code> should be set at the loader.
	 * <p>
	 * @param		args - the first parameter (args[0]) specifices the <code>ServiceManager</code>		
	 * @see		    com.sun.star.lang.XInitialization
	 * @see		    com.sun.star.lang.ServiceManager
	 */
    public void initialize( java.lang.Object[] args ) 
            throws com.sun.star.uno.Exception, 
                   com.sun.star.uno.RuntimeException
    {      
        if (args.length == 0) throw new com.sun.star.lang.IllegalArgumentException("No arguments specified");
        
        try {
            multiServiceFactory = (XMultiServiceFactory) AnyConverter.toObject(
                new Type(XMultiServiceFactory.class), args[0]);
        } 
        catch (ClassCastException castEx) {
            throw new com.sun.star.lang.IllegalArgumentException(
                "The argument must be an instance of XMultiServiceFactory");
        }
    }

	/**
	 * Supplies the implementation name of the component.
	 * <p>
	 * @return      the implementation name - here the class name
	 * @see			com.sun.star.lang.XServiceInfo
	 */
	public String getImplementationName() 
            throws com.sun.star.uno.RuntimeException 
    {
		return getClass().getName();
	}
    
	/**
	 * Verifies if a given service is supported by the component.
	 * <p>
	 * @return		true,if service is suported - otherwise false     
	 * @param		serviceName		the name of the service that should be checked
	 * @see			com.sun.star.lang.XServiceInfo
	 */
	public boolean supportsService(String serviceName) 
	        throws com.sun.star.uno.RuntimeException 
	{
		for ( int i = 0; i < supportedServices.length; i++ ) {
			if ( supportedServices[i].equals(serviceName) )
				return true;
		}
		return false;
	}
    
	/**
	 * Supplies a list of all service names supported by the component
	 * <p>
	 * @return		a String array with all supported services     
	 * @see			com.sun.star.lang.XServiceInfo
	 */
	public String[] getSupportedServiceNames() 
	        throws com.sun.star.uno.RuntimeException 
	{
		return supportedServices;
	}
		
	/**
	 * Provides a components factory.
	 * The <code>JavaLoader</code> tries to load the class first. If a loacation URL is given the 
	 * RegistrationClassFinder is used to load the class. Otherwise the class is loaded thru the Class.forName
	 * method.
	 * To get the factory the inspects the class for the optional static member functions __getServiceFactory resp. 
	 * getServiceFactory (DEPRECATED).
	 * If the function can not be found a default factory @see ComponentFactoryWrapper will be created.
	 * <p>
	 * @return		the factory for the component (@see com.sun.star.lang.XSingleServiceFactory)
	 * @param      	implementationName			the implementation (class) name of the component
	 * @param      	implementationLoaderUrl		the URL of the implementation loader. Not used.
	 * @param      	locationUrl					points to an archive (JAR file) which contains a component
	 * @param      	xKey	 					
	 * @see		   	com.sun.star.lang.XImplementationLoader
	 * @see			com.sun.star.com.loader.RegistrationClassFinder
	 */
	public java.lang.Object activate( String implementationName, 
	                                  String implementationLoaderUrl, 
	                                  String locationUrl, 
	                                  XRegistryKey xKey ) 
        throws CannotActivateFactoryException, 
               com.sun.star.uno.RuntimeException              
    {
        locationUrl = expand_url( locationUrl );
        
		Object returnObject  = null;
	    Class clazz  ;
	    
	    DEBUG("try to get factory for " + implementationName);
	    
	    // first we must get the class of the implementation
	    // 1. If a location URL is given it is assumed that this points to a JAR file.
	    //    The components class name is stored in the manifest file.
	    // 2. If only the implementation name is given, the class is loaded with the
	    //    Class.forName() method. This is a hack to load bootstrap components.
        //    Normally a string must no be null.
	    try {
		    if ( locationUrl != null ) {
		    	// 1.
	    		clazz = RegistrationClassFinder.find( locationUrl );
		    }
		    else {
		    	// 2.
	    		clazz = Class.forName( implementationName );
			}	
		}
		catch (java.net.MalformedURLException e) {
			CannotActivateFactoryException cae = new CannotActivateFactoryException(
					"Can not activate factory because " + e.toString() );
			cae.fillInStackTrace();
			throw cae;
		}
		catch (java.io.IOException e) {
			CannotActivateFactoryException cae = new CannotActivateFactoryException(
					"Can not activate factory because " + e.toString() );
			cae.fillInStackTrace();
			throw cae;
		}
		catch (java.lang.ClassNotFoundException e) {
			CannotActivateFactoryException cae = new CannotActivateFactoryException(
					"Can not activate factory because " + e.toString() );
			cae.fillInStackTrace();
			throw cae;
		}

        if (null == clazz)
        {
            CannotActivateFactoryException cae =
                new CannotActivateFactoryException(
                    "Cannot determine activation class!" );
			cae.fillInStackTrace();
			throw cae;
        }
        
		Class[] paramTypes = {String.class, XMultiServiceFactory.class, XRegistryKey.class};				                    		
		Object[] params = { implementationName, multiServiceFactory, xKey };
		
        // try to get factory from implemetation class
        // latest style: use the public static method __getComponentFactory
        // - new style: use the public static method __getServiceFactory
        // - old style: use the public static method getServiceFactory ( DEPRECATED )

        Method compfac_method = null;
		try
        {
		 	compfac_method = clazz.getMethod(
                "__getComponentFactory", new Class [] { String.class } );
		}
		catch ( NoSuchMethodException noSuchMethodEx) {} 
		catch ( SecurityException secEx) {}
        
        Method method = null;
        if (null == compfac_method)
        {
            try {
                method = clazz.getMethod("__getServiceFactory", paramTypes);
            } 
            catch ( NoSuchMethodException noSuchMethodEx) {
                method = null;
            } 
            catch ( SecurityException secEx) {
                method = null;
            }
        }
        
		try {
            if (null != compfac_method)
            {
                Object ret = compfac_method.invoke( clazz, new Object [] { implementationName } );
                if (null == ret || !(ret instanceof XSingleComponentFactory))
                {
                    throw new CannotActivateFactoryException(
                        "No factory object for " + implementationName );
                }
                return (XSingleComponentFactory)ret;
            }
            else
            {
                if ( method == null ) {
                    method = clazz.getMethod("getServiceFactory", paramTypes);
                }
                
                Object oRet = method.invoke(clazz, params);
                
                if ( (oRet != null) && (oRet instanceof XSingleServiceFactory) ) {
                    returnObject = (XSingleServiceFactory) oRet;						
                }
            }
    	}
		catch ( NoSuchMethodException e) {
            throw new CannotActivateFactoryException("Can not activate the factory for " 
                        + implementationName + " because " + e.toString() );
		} 
		catch ( SecurityException e) {
            throw new CannotActivateFactoryException("Can not activate the factory for " 
						+ implementationName + " because " + e.toString() );
		}
		catch ( IllegalAccessException e ) {
			throw new CannotActivateFactoryException("Can not activate the factory for " 
						+ implementationName + " because " + e.toString() );
		}
		catch ( IllegalArgumentException e ) {
			throw new CannotActivateFactoryException("Can not activate the factory for " 
						+ implementationName + " because " + e.toString() );
		}
		catch ( InvocationTargetException e ) {
			throw new CannotActivateFactoryException("Can not activate the factory for " 
						+ implementationName + " because " + e.getTargetException().toString() );
		}
	    
		return returnObject;
	}

	/**
	 * Registers the component in a registry under a given root key. If the component supports the optional
	 * methods __writeRegistryServiceInfo, writeRegistryServiceInfo (DEPRECATED), the call is delegated to that
	 * method. Otherwise a default registration will be accomplished.
	 * <p>
	 * @return		true if registration is successfully - otherwise false
	 * @param		regKey					the root key under that the component should be registred.
	 * @param		implementationLoaderUrl	specifies the loader, the component is loaded by.
	 * @param		locationUrl				points to an archive (JAR file) which contains a component
	 * @see 		ComponentFactoryWrapper
	 */
	public boolean writeRegistryInfo( XRegistryKey regKey, 
	                                  String implementationLoaderUrl, 
	                                  String locationUrl )
	        throws CannotRegisterImplementationException, 
	               com.sun.star.uno.RuntimeException
	{
        locationUrl = expand_url( locationUrl );
        
		boolean success = false;		
	    
        try {
            
    		Class clazz = RegistrationClassFinder.find(locationUrl);
            if (null == clazz)
            {
                throw new CannotRegisterImplementationException(
                    "Cannot determine registration class!" );
            }

			Class[] paramTypes = { XRegistryKey.class };
			Object[] params = { regKey };

			Method method  = clazz.getMethod("__writeRegistryServiceInfo", paramTypes);
			Object oRet = method.invoke(clazz, params);		    

			if ( (oRet != null) && (oRet instanceof Boolean) )
				success = ((Boolean) oRet).booleanValue();
        }	
		catch (Exception e) {
            throw new CannotRegisterImplementationException( e.getMessage());
 		}
		
		return success;
	}
	
	/**
	 * Supplies the factory for the <code>JavaLoader</code>
	 * <p>
	 * @return	the factory for the <code>JavaLoader</code>     
	 * @param	implName		the name of the desired component
	 * @param   multiFactory	the <code>ServiceManager</code> is delivered to the factory
	 * @param   regKey			not used - can be null
	 */
	public static XSingleServiceFactory getServiceFactory( String implName, 
	                                                       XMultiServiceFactory multiFactory, 
	                                                       XRegistryKey regKey)
	{	     
	    if ( implName.equals(JavaLoader.class.getName()) )
	        return new JavaLoaderFactory( multiFactory );
	    
	    return null;
	}
	
	/**
	 * Registers the <code>JavaLoader</code> at the registry.
	 * <p>
	 * @return     true if registration succseeded - otherwise false
	 * @param      regKey	root key under which the <code>JavaLoader</code> should be regidstered
	 */
	public static boolean writeRegistryServiceInfo(XRegistryKey regKey) {
	    boolean result = false;
	    
	    try {
	        XRegistryKey newKey = regKey.createKey("/" + JavaLoader.class.getName() + "/UNO/SERVICE");
	       
	        for (int i=0; i<supportedServices.length; i++)
	            newKey.createKey(supportedServices[i]);
	        
	        result = true;
	    }
	    catch (Exception ex) {
	        if (DEBUG) System.err.println(">>>JavaLoader.writeRegistryServiceInfo " + ex);
	    }
	    
	    return result;
    }
}