package org.openswing.swing.pivottable.tablemodelreaders.server;

import java.util.*;

import javax.swing.event.*;
import javax.swing.table.*;
import java.lang.reflect.Method;
import org.openswing.swing.message.receive.java.ValueObject;
import java.math.BigDecimal;
import javax.swing.ImageIcon;
import javax.swing.Icon;

/**
 * <p>Title: OpenSwing Framework</p>
 * <p>Description: TableModel implementation, based on a list of ValueObjects.</p>
 * <p>Copyright: Copyright (C) 2006 Mauro Carniel</p>
 *
 * <p> This file is part of OpenSwing Framework.
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the (LGPL) Lesser General Public
 * License as published by the Free Software Foundation;
 *
 *                GNU LESSER GENERAL PUBLIC LICENSE
 *                 Version 2.1, February 1999
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free
 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *       The author may be contacted at:
 *           maurocarniel@tin.it</p>
 *
 * @author Mauro Carniel
 * @version 1.0
 */
public class VOTableModel extends AbstractTableModel {

  /** list of ValueObjects */
  private List valueObjects;

  /** ValueObject setter methods */
  private Hashtable voSetterMethods = new Hashtable();

  /** ValueObject getter methods */
  private Hashtable voGetterMethods = new Hashtable();

  /** ValueObject type */
  private Class valueObjectType = null;

  /** collection of couples: related attribute (String), column model index (Integer) */
  private Hashtable reverseIndexes = new Hashtable();

  /** attribute names */
  private String[] attributeNames = null;


  /**
   * Constructs a TableModel, starting from the specified list of ValueObjects, having the specified list of attributes
   * @param valueObjects list of ValueObjects
   * @param attributeNames nome of attributes defined within the ValueObjects, used to define the TableModel
   */
  public VOTableModel(List valueObjects,String[] attributeNames) {
    this.valueObjects = valueObjects;
    this.attributeNames = attributeNames;
    this.valueObjectType = valueObjects.get(0).getClass();
    analyzeClassFields(new Hashtable(),"",new Method[0],valueObjectType);
  }


  /**
   * @param row row number
   * @return ValueObject
   */
  public final ValueObject getValueObject(int row) {
    return (ValueObject)valueObjects.get(row);
  }


  /**
   * Analyze class fields and fill in "voSetterMethods","voGetterMethods","indexes",reverseIndexes" attributes.
   * @param prefix e.g. "attrx.attry."
   * @param parentMethods getter methods of parent v.o.
   * @param classType class to analyze
   */
  private void analyzeClassFields(Hashtable vosAlreadyProcessed,String prefix,Method[] parentMethods,Class classType) {
    try {
      Integer num = (Integer)vosAlreadyProcessed.get(classType);
      if (num==null)
        num = new Integer(0);
      num = new Integer(num.intValue()+1);
      if (num.intValue()>10)
        return;
      vosAlreadyProcessed.put(classType,num);

      // retrieve all getter and setter methods defined in the specified value object...
      String attributeName = null;
      Method[] methods = classType.getMethods();
      String aName = null;
      for(int i=0;i<methods.length;i++) {
        attributeName = methods[i].getName();

        if (attributeName.startsWith("get") && methods[i].getParameterTypes().length==0 &&
          ValueObject.class.isAssignableFrom(methods[i].getReturnType())) {
          aName = getAttributeName(attributeName,classType);
          Method[] newparentMethods = new Method[parentMethods.length+1];
          System.arraycopy(parentMethods,0,newparentMethods,0,parentMethods.length);
          newparentMethods[parentMethods.length] = methods[i];
          analyzeClassFields(vosAlreadyProcessed,prefix+aName+".",newparentMethods,methods[i].getReturnType());
        }

        if (attributeName.startsWith("get") && methods[i].getParameterTypes().length==0 &&
            (methods[i].getReturnType().equals(String.class) ||
             methods[i].getReturnType().equals(Long.class) ||
             methods[i].getReturnType().equals(Long.TYPE) ||
             methods[i].getReturnType().equals(Float.class) ||
             methods[i].getReturnType().equals(Float.TYPE) ||
             methods[i].getReturnType().equals(Short.class) ||
             methods[i].getReturnType().equals(Short.TYPE) ||
             methods[i].getReturnType().equals(Double.class) ||
             methods[i].getReturnType().equals(Double.TYPE) ||
             methods[i].getReturnType().equals(BigDecimal.class) ||
             methods[i].getReturnType().equals(java.util.Date.class) ||
             methods[i].getReturnType().equals(java.sql.Date.class) ||
             methods[i].getReturnType().equals(java.sql.Timestamp.class) ||
             methods[i].getReturnType().equals(Integer.class) ||
             methods[i].getReturnType().equals(Integer.TYPE) ||
             methods[i].getReturnType().equals(Character.class) ||
             methods[i].getReturnType().equals(Boolean.class) ||
             methods[i].getReturnType().equals(boolean.class) ||
             methods[i].getReturnType().equals(ImageIcon.class) ||
             methods[i].getReturnType().equals(Icon.class) ||
             methods[i].getReturnType().equals(byte[].class) ||
             methods[i].getReturnType().equals(Object.class) ||
             ValueObject.class.isAssignableFrom( methods[i].getReturnType() )
            )) {
         attributeName = getAttributeName(attributeName,classType);
//          try {
//            if (classType.getMethod("set"+attributeName.substring(0,1).toUpperCase()+attributeName.substring(1),new Class[]{methods[i].getReturnType()})!=null)
          Method[] newparentMethods = new Method[parentMethods.length+1];
          System.arraycopy(parentMethods,0,newparentMethods,0,parentMethods.length);
          newparentMethods[parentMethods.length] = methods[i];
          voGetterMethods.put(prefix+attributeName,newparentMethods);
//          } catch (NoSuchMethodException ex) {
//          }
        }
        else if (attributeName.startsWith("is") &&
                 methods[i].getParameterTypes().length==0 &&
                 (methods[i].getReturnType().equals(Boolean.class) ||
                  methods[i].getReturnType().equals(boolean.class) )) {
          attributeName = getAttributeName(attributeName,classType);
          Method[] newparentMethods = new Method[parentMethods.length+1];
          System.arraycopy(parentMethods,0,newparentMethods,0,parentMethods.length);
          newparentMethods[parentMethods.length] = methods[i];
          voGetterMethods.put(prefix+attributeName,newparentMethods);
        }
        else if (attributeName.startsWith("set") && methods[i].getParameterTypes().length==1) {
          attributeName = getAttributeName(attributeName,classType);
          try {
            if (classType.getMethod("get"+attributeName.substring(0,1).toUpperCase()+attributeName.substring(1),new Class[0])!=null) {
              Method[] newparentMethods = new Method[parentMethods.length+1];
              System.arraycopy(parentMethods,0,newparentMethods,0,parentMethods.length);
              newparentMethods[parentMethods.length] = methods[i];
              voSetterMethods.put(prefix+attributeName,newparentMethods);
            }
          } catch (NoSuchMethodException ex) {
            try {
              if (classType.getMethod("is"+attributeName.substring(0,1).toUpperCase()+attributeName.substring(1),new Class[0])!=null) {
                Method[] newparentMethods = new Method[parentMethods.length+1];
                System.arraycopy(parentMethods,0,newparentMethods,0,parentMethods.length);
                newparentMethods[parentMethods.length] = methods[i];
                voSetterMethods.put(prefix+attributeName,newparentMethods);
              }
            } catch (NoSuchMethodException exx) {
            }
          }
        }
      }

      // fill in indexes with the colProperties indexes first; after them, it will be added the other indexes (of attributes not mapped with grid column...)
      HashSet alreadyAdded = new HashSet();
      int i=0;
      for(i=0;i<attributeNames.length;i++) {
        reverseIndexes.put(attributeNames[i],new Integer(i));
        alreadyAdded.add(attributeNames[i]);
      }
      Enumeration en = voGetterMethods.keys();
      while(en.hasMoreElements()) {
        attributeName = en.nextElement().toString();
        if (!alreadyAdded.contains(attributeName)) {
          reverseIndexes.put(attributeName,new Integer(i));
          i++;
        }
      }
    }
    catch (Exception ex) {
      ex.printStackTrace();
    }
  }


  /**
   * @param methodName getter method
   * @param clazz value object class
   * @return attribute name related to the specified getter method
   */
  private String getAttributeName(String methodName,Class classType) {
    String attributeName = null;
    if (methodName.startsWith("is"))
      attributeName = methodName.substring(2,3).toLowerCase()+(methodName.length()>3?methodName.substring(3):"");
    else
      attributeName = methodName.substring(3,4).toLowerCase()+(methodName.length()>4?methodName.substring(4):"");

    // an attribute name "Xxxx" becomes "xxxx" and this is not correct!
    try {
      Class c = classType;
      boolean attributeFound = false;
      while(!c.equals(Object.class)) {
        try {
          c.getDeclaredField(attributeName);
          attributeFound = true;
          break;
        }
        catch (Throwable ex2) {
          c = c.getSuperclass();
        }
      }
      if (!attributeFound) {
        // now trying to find an attribute having the first character in upper case (e.g. "Xxxx")
        String name = attributeName.substring(0,1).toUpperCase()+attributeName.substring(1);
        c = classType;
        while(!c.equals(Object.class)) {
          try {
            c.getDeclaredField(name);
            attributeFound = true;
            break;
          }
          catch (Throwable ex2) {
            c = c.getSuperclass();
          }
        }
        if (attributeFound)
          attributeName = name;
      }
    }
    catch (Throwable ex1) {
    }


    return attributeName;
  }


  public final int getColumnCount() {
    return attributeNames.length;
  }


  public final int getRowCount() {
    return valueObjects.size();
  }


  public final boolean isCellEditable(int rowIndex, int columnIndex) {
    return false;
  }


  public final Class getColumnClass(int columnIndex) {
    try {
      Method[] m = (Method[])voGetterMethods.get(attributeNames[columnIndex]);
      return m[m.length-1].getReturnType();
    }
    catch (Exception ex) {
      ex.printStackTrace();
      return String.class;
    }
  }


  public final Object getValueAt(int rowIndex, int colIndex) {
    try {
      Method[] m = (Method[])voGetterMethods.get(attributeNames[colIndex]);
      Object obj = valueObjects.get(rowIndex);
      for(int i=0;i<m.length-1;i++){
        obj = (ValueObject)m[i].invoke(obj,new Object[0]);
        if(obj == null) {
          return null;
        }
      }
      return m[m.length-1].invoke(obj,new Object[0]);
    }
    catch (Exception ex) {
      ex.printStackTrace();
      return null;
    }
  }


  /**
   * setValueAt
   *
   * @param aValue Object
   * @param rowIndex int
   * @param columnIndex int
   */
  public final void setValueAt(Object aValue, int rowIndex, int columnIndex) {
    throw new UnsupportedOperationException("TableModel is read only.");
  }


  /**
   * getColumnName
   *
   * @param columnIndex int
   * @return String
   */
  public final String getColumnName(int columnIndex) {
    return attributeNames[columnIndex];
  }



}
