/*
  Copyright (C) 2006 Helge Hess

  This file is part of JOPE.

  JOPE is free software; you can redistribute it and/or modify it under
  the terms of the GNU Lesser General Public License as published by the
  Free Software Foundation; either version 2, or (at your option) any
  later version.

  JOPE is distributed in the hope that it will be useful, but WITHOUT ANY
  WARRANTY; without even the implied warranty of MERCHANTABILITY or
  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
  License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with JOPE; see the file COPYING.  If not, write to the
  Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
  02111-1307, USA.
*/

package org.opengroupware.jope.foundation;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.opengroupware.jope.foundation.kvc.MissingPropertyException;

/*
 * NSJavaRuntime
 * 
 * Convenience functions to deal with the Java runtime. Also check out the
 * NSSelector class.
 */
public class NSJavaRuntime {
  
  protected static final Log log = LogFactory.getLog("NSJavaRuntime");

  /* discovering classes */

  public static Class NSClassFromString(String _className) {
    if (_className == null) {
      if (log.isDebugEnabled())
        log.debug("NSClassFromString() was called with a null argument.");
      return null;
    }
    try {
      return Class.forName(_className);
    }
    catch (ClassNotFoundException e) {
      return null;
    }    
  }

  public static Class NSClassFromString(String _name, String[] _lookupPath) {
    Class clazz;
    
    /* first try exact match */
    if ((clazz = NSClassFromString(_name)) != null)
      return clazz;
    
    for (String prefix: _lookupPath) {
      if ((clazz = NSClassFromString(prefix + "." + _name)) != null)
        return clazz;
    }

    return null;
  }
  
  public static String NSStringFromClass(Class _clazz) {
    if (_clazz == null)
      return null;
    return _clazz.getName();
  }
  
  /* methods */
  
  public static Method NSMethodFromString
    (Class _cls, String _name, Class[] _signature, boolean _flat)
  {
    if (_cls == null || _name == null)
      return null;
    
    try {
      Method m = _cls.getMethod(_name, _signature);
      if (m != null) return m;
    }
    catch (NoSuchMethodException nsme) {
      /* we detect errors using the null handle */
    }
    
    if (_flat) return null;
    
    /* continue search at superclass */
    
    while ((_cls = _cls.getSuperclass()) != null) {
      try {
        //System.err.println("check " + _name + " in " + _cls);
        Method m = _cls.getMethod(_name, _signature);
        if (m != null) return m;
      }
      catch (NoSuchMethodException nsme) {
      }
    }
    return null;
  }
  
  public static Method NSMethodFromString(Class _cls, String _name, Class[] _s){
    return NSMethodFromString(_cls, _name, _s, false /* deep search */);
  }
  
  /* instantiating objects */
  
  protected static final Class[]  EmptyClassArray  = { };
  protected static final Object[] EmptyObjectArray = { };

  public static Object NSAllocateObject(Class _cls) {
    return NSAllocateObject(_cls, EmptyClassArray /* types */, null /* args */);
  }
  
  public static Object NSAllocateObject(Class _cls, Object _arg0) {
    return NSAllocateObject(_cls, EmptyClassArray /* types */,
                            new Object[] { _arg0 });
  }

  public static Object NSAllocateObject(Class _cls, Class _t0, Object _a0) {
    return NSAllocateObject(_cls, new Class[] { _t0 }, new Object[] { _a0 });
  }
  
  public static Object NSAllocateObject
    (Class _cls, Class[] _argTypes, Object[] _args)
  {
    if (_cls == null)
      return null;
    
    /* prepare arguments */
    
    if (_argTypes == EmptyClassArray)
      _argTypes = null; /* will get recreated when appropriate */
    
    if (_args == null && _argTypes == null) {
      _args     = EmptyObjectArray;
      _argTypes = EmptyClassArray;
    }
    else if (_argTypes == null) {
      /* derive argument types from arguments */
      _argTypes = new Class[_args.length];
      for (int i = 0; i < _args.length; i++) {
        if (_args[i] == null) {
          /* can't properly derive class from null */
          _argTypes[i] = Object.class;
        }
        else
          _argTypes[i] = _args[i].getClass();
      }
    }
    else if (_args == null) {
      /* creating an object already clears the values to 'null'? */
      _args = new Object[_argTypes.length];
    }
    
    /* find constructor */
    
    Constructor ctor = null;
    
    try {
      /* TODO: this only does an EXACT match. Eg you can't pass in subclasses
       *       of arguments (eg won't find a constructor taking a WOApplication
       *       if the argument type is a WOApplication subclass).
       */ 
      ctor = _cls.getConstructor(_argTypes);
    }
    catch (SecurityException e) {
      log.error("cannot access constructor of object", e);
      return null;
    }
    catch (NoSuchMethodException e) {
      log.error("did not find a proper constructor for object", e);
      return null;
    }
    if (ctor == null) { /* can't happen? */
      log.error("got no constructor");
      return null;
    }
    
    /* instantiate object */
    
    Object result = null;
    
    try {
      result = ctor.newInstance(_args);
    }
    catch (IllegalArgumentException e) {
      log.error("illegal argument for constructor", e);
      return null;
    }
    catch (InstantiationException e) {
      log.error("could not instantiate object", e);
      return null;
    }
    catch (IllegalAccessException e) {
      log.error("illegal access", e);
      return null;
    }
    catch (InvocationTargetException e) {
      log.error("constructor failed with an exception", e.getCause());
      return null;
    }
    
    return result;
  }
  
  /* basic values */
  
  public static boolean boolValueForObject(Object v) {
    if (v == null)
      return false;
    
    if (v instanceof String) {
      if (v.equals(""))      return false;
      if (v.equals("NO"))    return false;
      if (v.equals("false")) return false;
      if (v.equals("0"))     return false;
      return true;
    }
    
    if (v instanceof Boolean)
      return ((Boolean)v).booleanValue();
    
    if (v instanceof Number)
      return (((Number)v).intValue() == 0) ? false : true;
    
    return true;
  }
  public static boolean boolValueForKey(Object _v, String _key) {
    if (_v == null || _key == null)
      return false;
    
    if (_v instanceof NSKeyValueCodingAdditions)
      _v = ((NSKeyValueCodingAdditions)_v).valueForKeyPath(_key);
    else
      _v = NSKeyValueCodingAdditions.Utility.valueForKeyPath(_v, _key);
    return boolValueForObject(_v);
  }
  
  public static int intValueForObject(Object v) {
    if (v == null)
      return 0;
    
    if (v instanceof Number)
      return ((Number)v).intValue();
    
    if (v instanceof String) {
      /* Note: we return 0 for empty strings */
      if (((String)v).length() == 0)
        return 0;
      
      try {
        return Integer.parseInt((String)v);
      }
      catch (NumberFormatException e) {
        return 0;
      }
    }
    
    return intValueForObject(v.toString());
  }
  public static int intValueForKey(Object _v, String _key) {
    if (_v == null || _key == null)
      return 0;
    
    if (_v instanceof NSKeyValueCodingAdditions)
      _v = ((NSKeyValueCodingAdditions)_v).valueForKeyPath(_key);
    else {
      try {
        _v = NSKeyValueCodingAdditions.Utility.valueForKeyPath(_v, _key);
      }
      catch (MissingPropertyException e) {
        /* if we invoke KVC on those, we can get the exception */
        _v = 0;
      }
    }
    return intValueForObject(_v);
  }
  
  public static String stringValueForObject(Object v) {
    if (v == null)
      return null;
    
    if (v instanceof String)
      return (String)v;
    
    return v.toString();    
  }
  public static String stringValueForKey(Object _v, String _key) {
    if (_v == null || _key == null)
      return null;
    
    if (_v instanceof NSKeyValueCodingAdditions)
      _v = ((NSKeyValueCodingAdditions)_v).valueForKeyPath(_key);
    else
      _v = NSKeyValueCodingAdditions.Utility.valueForKeyPath(_v, _key);
    return stringValueForObject(_v);
  }
}
