package genesom;

/**
 * ReflectProperties.java
 * ----------------------
 * @author Sascha L. Teichmann
 * @version 0.1
 *
 * Changes: Oct 2002
 *   adding recursive initialization recursiveInitThruSetMethods()
 *   adding refprop.reflectproperties.reflect to log set-method calls
 *   adding toString() method
 *
 */
//package refprop;

import java.util.Properties;
import java.util.Hashtable;
import java.util.Enumeration;

import java.lang.reflect.Method;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.InvocationTargetException;

public class ReflectProperties
  extends    Properties
{
  protected Hashtable wrappers = new Hashtable();

  { wrappers.put(double.class,  new Object[] { PRIMITIVE_WRAPPER, Double.class    });
    wrappers.put(float.class,   new Object[] { PRIMITIVE_WRAPPER, Float.class     });
    wrappers.put(byte.class,    new Object[] { PRIMITIVE_WRAPPER, Byte.class      });
    wrappers.put(short.class,   new Object[] { PRIMITIVE_WRAPPER, Short.class     });
    wrappers.put(int.class,     new Object[] { PRIMITIVE_WRAPPER, Integer.class   });
    wrappers.put(long.class,    new Object[] { PRIMITIVE_WRAPPER, Long.class      });
    wrappers.put(boolean.class, new Object[] { PRIMITIVE_WRAPPER, Boolean.class   });
    wrappers.put(char.class,    new Object[] { CHAR_WRAPPER,      Character.class });
    wrappers.put(String.class,  new Object[] { STRING_WRAPPER,    String.class    });
  }

  protected static final Class [] STRING_PARAM = new Class[] { String.class };

  /**
   * System Property, um Fehlermeldungen vom ProblemHandler auf Standard Error zubekommen.
   */
  protected static final String DEBUG = "refprop.reflectproperties.debug";

  /**
   * System Property, um Key = Value auf Standard Error reflektiert zu bekommen.
   */
  protected static final String REFLECT = "refprop.reflectproperties.reflect";

  /**
   *
   */
  public interface ParameterWrapper {
    Object wrap(String string, Class required) throws Exception;
  }

  /**
   * Umwandlung des Property-Strings in die benötigte CLASS
   * @param string Der String der in eine andere Class umbewandelt werden muß.
   * @param required die benötigte Klasse in die umgewandelt werden soll.
   */
  public static final ParameterWrapper PRIMITIVE_WRAPPER = new ParameterWrapper() {
    public Object wrap(String string, Class required) throws Exception {

      try {
        return required.getConstructor(STRING_PARAM).newInstance(new Object[] { string });
      }
      catch (InvocationTargetException ite) {
        // Abfangen der NumberFormatExceptions etc.
        Throwable t = ite.getTargetException();
        if (t instanceof Error    ) throw (Error)t;
        if (t instanceof Exception) throw (Exception)t;
        throw ite;
      }
    }
  };

  // Simple Wrapper
  public static final ParameterWrapper STRING_WRAPPER = new ParameterWrapper() {
    public Object wrap(String string, Class required) throws Exception {
      return string;
    }
  };

  public static final ParameterWrapper CHAR_WRAPPER = new ParameterWrapper() {
    public Object wrap(String string, Class required) throws Exception {
      return new Character(string.charAt(0));
    }
  };

  // mögliche Probleme beim Initialisieren
  public interface ProblemHandler {
    boolean notFound(String property);
    boolean foundButEmpty(String property);
    boolean noWrapperFound(Class clazz);
    boolean exceptionDuringWrapping(String property, Exception exception);
    boolean exceptionDuringSetting(String method, Exception exception);
  }

  // Adapter um ggf. nicht alle Fehlerbehandlungen impl. zu müssen
  public static class ProblemHandlerAdapter implements ProblemHandler {
    public boolean notFound(String property)                                     { return true; }
    public boolean foundButEmpty(String property)                                { return true; }
    public boolean noWrapperFound(Class clazz)                                   { return true; }
    public boolean exceptionDuringWrapping(String property, Exception exception) { return true; }
    public boolean exceptionDuringSetting(String method, Exception exception)    { return true; }
  }

  // sehr toleranter ProblemHandler
  protected static final ProblemHandler defaultPH = new ProblemHandler() {

    // Es wurde in den Properties nach Property mit Namen 'property' gesucht und nicht gefunden.
    public boolean notFound(String property) {
      if (Boolean.getBoolean(DEBUG))
        System.err.println("Cannot find the property  '" + property + "'.");
      return true;
    }

    // Es wurde zwar eine geeignete Property gefunden, aber der Wert ist ein Leerstring.
    public boolean foundButEmpty(String property) {
      if (Boolean.getBoolean(DEBUG))
        System.err.println("property '" + property + "' was found but it has no value.");
      return true;
    }

    // Es liegt kein Wrapper für den Zieltyp vor.
    public boolean noWrapperFound(Class clazz) {
      if (Boolean.getBoolean(DEBUG))
        System.err.println("Cannot wrap type '" + clazz +"'.");
      return true;
    }

    // Bei der Konvertierungen in den Zieltyp ist ein Fehler aufgetreten.
    public boolean exceptionDuringWrapping(String property, Exception exception) {
      if (Boolean.getBoolean(DEBUG))
        System.err.println("Wrapping property '" + property + "' raised '" + exception + "'.");
      return true;
    }

    // Setzen des Feldes ging daneben.
    public boolean exceptionDuringSetting(String method, Exception exception) {
      if (Boolean.getBoolean(DEBUG))
        System.err.println("Setting/calling field/method '" + method + "' raised '" + exception + "'.");
      return true;
    }
  };

  /**
   *
   */
  public ReflectProperties() {}
  public ReflectProperties(Properties defaults) { super(defaults); }

  /**
   * Initialisiert alle Public Felder die nicht static sind mit den Default Properties
   *
   */
  public boolean initPublicFields(Object object) {
    return initPublicFields(object, defaultPH);
  }

  public boolean initPublicFields(Object object, ProblemHandler probHandler) {

    if (object == null) throw new IllegalArgumentException("real object needed.");

    if (probHandler == null) probHandler = defaultPH;

    Field [] fields = object.getClass().getFields();
    Field    field;

    Class clazz;

    Object [] wh;    // Wrapper -> Class
    Object    value;

    String name, paramString, paramStringProp;

    for (int i = 0; i < fields.length; ++i) {
      if (Modifier.isStatic((field = fields[i]).getModifiers())) continue;

      if ((wh = (Object[])wrappers.get(clazz = field.getType())) != null) {
        name = field.getName();
        //paramString = Character.toLowerCase(name.charAt(0)) + name.substring(1);
        paramString = name;
        paramStringProp = getProperty(paramString);

        // keine Property vorhanden.
        if (paramStringProp == null)
          if (!probHandler.notFound(paramString)) return false;
          else continue;

        // Property ist ein Leerstring.
        if (paramStringProp.length() == 0)
          if (!probHandler.foundButEmpty(paramString)) return false;
          else continue;

        // Möglichkeit, daß beim Umwandeln des String in benötigten Typ ein Fehler auftritt.
        try { value = ((ParameterWrapper)wh[0]).wrap(paramStringProp, (Class)wh[1]); }
        catch (Exception e) {
          if (!probHandler.exceptionDuringWrapping(paramString, e)) return false;
          else continue;
        }

        // Möglichkeit, daß beim Setzen Fehler auftritt.
        try { field.set(object, value); }
        catch (Exception e) {
          if (!probHandler.exceptionDuringSetting(paramString, e)) return false;
          else continue;
        }
      }
      // benötigter Wrapper nicht vorhanden.
      else if (!probHandler.noWrapperFound(clazz)) return false;
    }

    return true;
  }

  public boolean initThruSetMethods(Object object) {
    return initThruSetMethods(object, defaultPH);
  }

  /**
  * @return TRUE
  * @param Objekt Das Objekt das initialisiert werden soll.
  * @param ProblemHandler ProblemHandler Objekt falls spezielle Fehlerbehandlung benötigt wird.
  */
  public boolean initThruSetMethods(Object object, ProblemHandler probHandler) {

    if (object == null) throw new IllegalArgumentException("real object needed.");

    if (probHandler == null) probHandler = defaultPH;

    Method [] methods  = object.getClass().getMethods();
    Method    method;

    Class [] params;

    Object [] invokeParms = new Object[1], wh;

    Exception exception;

    String name, paramString, paramStringProp;

    for (int i = 0; i < methods.length; ++i)
      if ((name = (method = methods[i]).getName()).length() > 3
        && name.startsWith("set")
        && (params = method.getParameterTypes()).length == 1
      ) {
        if ((wh = (Object[])wrappers.get(params[0])) != null) {

          paramString = name.substring(3);

          paramStringProp = getProperty(paramString);

          if (paramStringProp == null) {
            paramString = Character.toLowerCase(name.charAt(3)) + name.substring(4);
            paramStringProp = getProperty(paramString);
          }

          if (paramStringProp == null)
            if (!probHandler.notFound(paramString)) return false;
            else continue;

          if (paramStringProp.length() == 0)
            if (!probHandler.foundButEmpty(paramString)) return false;
            else continue;

          try {
            invokeParms[0] = ((ParameterWrapper)wh[0]).wrap(paramStringProp, (Class)wh[1]);
            if (Boolean.getBoolean(REFLECT))
              System.err.println("## " + paramString + " = " + paramStringProp);
          }
          catch (Exception e) {
            if (!probHandler.exceptionDuringWrapping(paramString, e)) return false;
            else continue;
          }
          exception = null;

          try { method.invoke(object, invokeParms); }
          catch (InvocationTargetException ite) {
            Throwable t = ite.getTargetException();
            if (t instanceof Error) throw (Error)t;
            exception = t instanceof Exception ? (Exception)t : ite;
          }
          catch (Exception e) { exception = e; }

          if (exception != null && !probHandler.exceptionDuringSetting(name, exception))
            return false;
        }
        else  // no wrapper found
          if (!probHandler.noWrapperFound(params[0])) return false;

      } // for all methods with relevant signature

    return true;
  }

  public boolean recursiveInitThruSetMethods(
      Object object,
      ProblemHandler probHandler,
      Hashtable known_objects
  ) {

    if(known_objects.containsKey(object)) return true;

    known_objects.put(object, Boolean.TRUE);


    if(!initThruSetMethods(object, probHandler)) return false;

    Field[] fields = object.getClass().getFields();

    for(int i = 0; i < fields.length; i++) {

      try {

        if(fields[i].getType().isPrimitive()) continue;

        if(!recursiveInitThruSetMethods(fields[i].get(object),
                                        probHandler,
                                        known_objects)) return false;
      }
      catch(IllegalAccessException iae) { return false; }
    }

    return true;
  }

  public boolean recursiveInitThruSetMethods(Object object) {
    return recursiveInitThruSetMethods(object, defaultPH, new Hashtable());
  }

  public void setWrapper(Class clazz, ParameterWrapper wrapper) {
    wrappers.put(clazz, new Object[] { wrapper, clazz });
  }


  public String toString() {

    String ls = System.getProperties().getProperty("line.separator");

    Object[] os = entrySet().toArray();

    java.util.Arrays.sort(os, new java.util.Comparator() {
      public int compare(Object o1, Object o2) {
        return (o1.toString()).compareTo(o2.toString());
      }
    });

    StringBuffer sb = new StringBuffer();

    for(int i = 0; i < os.length; i++)
      sb.append("# ").append(os[i].toString()).append(ls);

    return sb.toString();
  }

}
// end of file
