package app.termora;/*
 * @(#)SwingUtils.java	1.02 11/15/08
 *
 */
//package darrylbu.util;

import javax.swing.*;
import java.awt.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.*;

/**
 * A collection of utility methods for Swing.
 *
 * @author Darryl Burke
 */
public final class SwingUtils {

   private SwingUtils() {
      throw new Error("SwingUtils is just a container for static methods");
   }

   /**
    * Convenience method for searching below <code>container</code> in the
    * component hierarchy and return nested components that are instances of
    * class <code>clazz</code> it finds. Returns an empty list if no such
    * components exist in the container.
    * <P>
    * Invoking this method with a class parameter of JComponent.class
    * will return all nested components.
    * <P>
    * This method invokes getDescendantsOfType(clazz, container, true)
    * 
    * @param clazz the class of components whose instances are to be found.
    * @param container the container at which to begin the search
    * @return the List of components
    */
   public static <T extends JComponent> List<T> getDescendantsOfType(
         Class<T> clazz, Container container) {
      return getDescendantsOfType(clazz, container, true);
   }

   /**
    * Convenience method for searching below <code>container</code> in the
    * component hierarchy and return nested components that are instances of
    * class <code>clazz</code> it finds. Returns an empty list if no such
    * components exist in the container.
    * <P>
    * Invoking this method with a class parameter of JComponent.class
    * will return all nested components.
    * 
    * @param clazz the class of components whose instances are to be found.
    * @param container the container at which to begin the search
    * @param nested true to list components nested within another listed
    * component, false otherwise
    * @return the List of components
    */
   public static <T extends JComponent> List<T> getDescendantsOfType(
         Class<T> clazz, Container container, boolean nested) {
      List<T> tList = new ArrayList<T>();
      for (Component component : container.getComponents()) {
         if (clazz.isAssignableFrom(component.getClass())) {
            tList.add(clazz.cast(component));
         }
         if (nested || !clazz.isAssignableFrom(component.getClass())) {
            tList.addAll(SwingUtils.<T>getDescendantsOfType(clazz,
                  (Container) component, nested));
         }
      }
      return tList;
   }

   /**
    * Convenience method that searches below <code>container</code> in the
    * component hierarchy and returns the first found component that is an
    * instance of class <code>clazz</code> having the bound property value.
    * Returns {@code null} if such component cannot be found.
    * <P>
    * This method invokes getDescendantOfType(clazz, container, property, value,
    * true)
    * 
    * @param clazz the class of component whose instance is to be found.
    * @param container the container at which to begin the search
    * @param property the className of the bound property, exactly as expressed in
    * the accessor e.g. "Text" for getText(), "Value" for getValue().
    * @param value the value of the bound property
    * @return the component, or null if no such component exists in the
    * container
    * @throws java.lang.IllegalArgumentException if the bound property does
    * not exist for the class or cannot be accessed
    */
   public static <T extends JComponent> T getDescendantOfType(
         Class<T> clazz, Container container, String property, Object value)
         throws IllegalArgumentException {
      return getDescendantOfType(clazz, container, property, value, true);
   }

   /**
    * Convenience method that searches below <code>container</code> in the
    * component hierarchy and returns the first found component that is an
    * instance of class <code>clazz</code> and has the bound property value.
    * Returns {@code null} if such component cannot be found.
    * 
    * @param clazz the class of component whose instance to be found.
    * @param container the container at which to begin the search
    * @param property the className of the bound property, exactly as expressed in
    * the accessor e.g. "Text" for getText(), "Value" for getValue().
    * @param value the value of the bound property
    * @param nested true to list components nested within another component
    * which is also an instance of <code>clazz</code>, false otherwise
    * @return the component, or null if no such component exists in the
    * container
    * @throws java.lang.IllegalArgumentException if the bound property does
    * not exist for the class or cannot be accessed
    */
   public static <T extends JComponent> T getDescendantOfType(Class<T> clazz,
         Container container, String property, Object value, boolean nested)
         throws IllegalArgumentException {
      List<T> list = getDescendantsOfType(clazz, container, nested);
      return getComponentFromList(clazz, list, property, value);
   }

   /**
    * Convenience method for searching below <code>container</code> in the
    * component hierarchy and return nested components of class
    * <code>clazz</code> it finds.  Returns an empty list if no such
    * components exist in the container.
    * <P>
    * This method invokes getDescendantsOfClass(clazz, container, true)
    * 
    * @param clazz the class of components to be found.
    * @param container the container at which to begin the search
    * @return the List of components
    */
   public static <T extends JComponent> List<T> getDescendantsOfClass(
         Class<T> clazz, Container container) {
      return getDescendantsOfClass(clazz, container, true);
   }

   /**
    * Convenience method for searching below <code>container</code> in the
    * component hierarchy and return nested components of class
    * <code>clazz</code> it finds.  Returns an empty list if no such
    * components exist in the container.
    * 
    * @param clazz the class of components to be found.
    * @param container the container at which to begin the search
    * @param nested true to list components nested within another listed
    * component, false otherwise
    * @return the List of components
    */
   public static <T extends JComponent> List<T> getDescendantsOfClass(
         Class<T> clazz, Container container, boolean nested) {
      List<T> tList = new ArrayList<T>();
      for (Component component : container.getComponents()) {
         if (clazz.equals(component.getClass())) {
            tList.add(clazz.cast(component));
         }
         if (nested || !clazz.equals(component.getClass())) {
            tList.addAll(SwingUtils.<T>getDescendantsOfClass(clazz,
                  (Container) component, nested));
         }
      }
      return tList;
   }

   /**
    * Convenience method that searches below <code>container</code> in the
    * component hierarchy in a depth first manner and returns the first
    * found component of class <code>clazz</code> having the bound property
    * value.
    * <P>
    * Returns {@code null} if such component cannot be found.
    * <P>
    * This method invokes getDescendantOfClass(clazz, container, property,
    * value, true)
    * 
    * @param clazz the class of component to be found.
    * @param container the container at which to begin the search
    * @param property the className of the bound property, exactly as expressed in
    * the accessor e.g. "Text" for getText(), "Value" for getValue().
    * This parameter is case sensitive.
    * @param value the value of the bound property
    * @return the component, or null if no such component exists in the
    * container's hierarchy.
    * @throws java.lang.IllegalArgumentException if the bound property does
    * not exist for the class or cannot be accessed
    */
   public static <T extends JComponent> T getDescendantOfClass(Class<T> clazz,
         Container container, String property, Object value)
         throws IllegalArgumentException {
      return getDescendantOfClass(clazz, container, property, value, true);
   }

   /**
    * Convenience method that searches below <code>container</code> in the
    * component hierarchy in a depth first manner and returns the first
    * found component of class <code>clazz</code> having the bound property
    * value.
    * <P>
    * Returns {@code null} if such component cannot be found.
    * 
    * @param clazz the class of component to be found.
    * @param container the container at which to begin the search
    * @param property the className of the bound property, exactly as expressed
    * in the accessor e.g. "Text" for getText(), "Value" for getValue().
    * This parameter is case sensitive.
    * @param value the value of the bound property
    * @param nested true to include components nested within another listed
    * component, false otherwise
    * @return the component, or null if no such component exists in the
    * container's hierarchy
    * @throws java.lang.IllegalArgumentException if the bound property does
    * not exist for the class or cannot be accessed
    */
   public static <T extends JComponent> T getDescendantOfClass(Class<T> clazz,
         Container container, String property, Object value, boolean nested)
         throws IllegalArgumentException {
      List<T> list = getDescendantsOfClass(clazz, container, nested);
      return getComponentFromList(clazz, list, property, value);
   }

   private static <T extends JComponent> T getComponentFromList(Class<T> clazz,
         List<T> list, String property, Object value)
         throws IllegalArgumentException {
      T retVal = null;
      Method method = null;
      try {
         method = clazz.getMethod("get" + property);
      } catch (NoSuchMethodException ex) {
         try {
            method = clazz.getMethod("is" + property);
         } catch (NoSuchMethodException ex1) {
            throw new IllegalArgumentException("Property " + property +
                  " not found in class " + clazz.getName());
         }
      }
      try {
         for (T t : list) {
            Object testVal = method.invoke(t);
            if (equals(value, testVal)) {
               return t;
            }
         }
      } catch (InvocationTargetException ex) {
         throw new IllegalArgumentException(
               "Error accessing property " + property +
               " in class " + clazz.getName());
      } catch (IllegalAccessException ex) {
         throw new IllegalArgumentException(
               "Property " + property +
               " cannot be accessed in class " + clazz.getName());
      } catch (SecurityException ex) {
         throw new IllegalArgumentException(
               "Property " + property +
               " cannot be accessed in class " + clazz.getName());
      }
      return retVal;
   }

   /**
    * Convenience method for determining whether two objects are either
    * equal or both null.
    * 
    * @param obj1 the first reference object to compare.
    * @param obj2 the second reference object to compare.
    * @return true if obj1 and obj2 are equal or if both are null,
    * false otherwise
    */
   public static boolean equals(Object obj1, Object obj2) {
      return obj1 == null ? obj2 == null : obj1.equals(obj2);
   }

   /**
    * Convenience method for mapping a container in the hierarchy to its
    * contained components.  The keys are the containers, and the values
    * are lists of contained components.
    * <P>
    * Implementation note:  The returned value is a HashMap and the values
    * are of type ArrayList.  This is subject to change, so callers should
    * code against the interfaces Map and List.
    * 
    * @param container The JComponent to be mapped
    * @param nested true to drill down to nested containers, false otherwise
    * @return the Map of the UI
    */
   public static Map<JComponent, List<JComponent>> getComponentMap(
         JComponent container, boolean nested) {
      HashMap<JComponent, List<JComponent>> retVal =
            new HashMap<JComponent, List<JComponent>>();
      for (JComponent component : getDescendantsOfType(JComponent.class,
            container, false)) {
         if (!retVal.containsKey(container)) {
            retVal.put(container,
                  new ArrayList<JComponent>());
         }
         retVal.get(container).add(component);
         if (nested) {
            retVal.putAll(getComponentMap(component, nested));
         }
      }
      return retVal;
   }

   /**
    * Convenience method for retrieving a subset of the UIDefaults pertaining
    * to a particular class.
    * 
    * @param clazz the class of interest
    * @return the UIDefaults of the class
    */
   public static UIDefaults getUIDefaultsOfClass(Class clazz) {
      String name = clazz.getName();
      name = name.substring(name.lastIndexOf(".") + 2);
      return getUIDefaultsOfClass(name);
   }

   /**
    * Convenience method for retrieving a subset of the UIDefaults pertaining
    * to a particular class.
    * 
    * @param className fully qualified name of the class of interest
    * @return the UIDefaults of the class named
    */
   public static UIDefaults getUIDefaultsOfClass(String className) {
      UIDefaults retVal = new UIDefaults();
      UIDefaults defaults = UIManager.getLookAndFeelDefaults();
      List<?> listKeys = Collections.list(defaults.keys());
      for (Object key : listKeys) {
         if (key instanceof String && ((String) key).startsWith(className)) {
            String stringKey = (String) key;
            String property = stringKey;
            if (stringKey.contains(".")) {
               property = stringKey.substring(stringKey.indexOf(".") + 1);
            }
            retVal.put(property, defaults.get(key));
         }
      }
      return retVal;
   }

   /**
    * Convenience method for retrieving the UIDefault for a single property
    * of a particular class.
    * 
    * @param clazz the class of interest
    * @param property the property to query
    * @return the UIDefault property, or null if not found
    */
   public static Object getUIDefaultOfClass(Class clazz, String property) {
      Object retVal = null;
      UIDefaults defaults = getUIDefaultsOfClass(clazz);
      List<Object> listKeys = Collections.list(defaults.keys());
      for (Object key : listKeys) {
         if (key.equals(property)) {
            return defaults.get(key);
         }
         if (key.toString().equalsIgnoreCase(property)) {
            retVal = defaults.get(key);
         }
      }
      return retVal;
   }
   
   /**
    * Exclude methods that return values that are meaningless to the user
    */
   static Set<String> setExclude = new HashSet<String>();
   static {
      setExclude.add("getFocusCycleRootAncestor");
      setExclude.add("getAccessibleContext");
      setExclude.add("getColorModel");
      setExclude.add("getGraphics");
      setExclude.add("getGraphicsConfiguration");
   }

   /**
    * Convenience method for obtaining most non-null human readable properties
    * of a JComponent.  Array properties are not included.
    * <P>
    * Implementation note:  The returned value is a HashMap.  This is subject
    * to change, so callers should code against the interface Map.
    * 
    * @param component the component whose proerties are to be determined
    * @return the class and value of the properties
    */
   public static Map<Object, Object> getProperties(JComponent component) {
      Map<Object, Object> retVal = new HashMap<Object, Object>();
      Class<?> clazz = component.getClass();
      Method[] methods = clazz.getMethods();
      Object value = null;
      for (Method method : methods) {
         if (method.getName().matches("^(is|get).*") &&
               method.getParameterTypes().length == 0) {
            try {
               Class returnType = method.getReturnType();
               if (returnType != void.class &&
                     !returnType.getName().startsWith("[") &&
                     !setExclude.contains(method.getName())) {
                  String key = method.getName();
                  value = method.invoke(component);
                  if (value != null && !(value instanceof Component)) {
                     retVal.put(key, value);
                  }
               }
            // ignore exceptions that arise if the property could not be accessed
            } catch (IllegalAccessException ex) {
            } catch (IllegalArgumentException ex) {
            } catch (InvocationTargetException ex) {
            }
         }
      }
      return retVal;
   }

   /**
    * Convenience method to obtain the Swing class from which this
    * component was directly or indirectly derived.
    * 
    * @param component The component whose Swing superclass is to be
    * determined
    * @return The nearest Swing class in the inheritance tree
    */
   public static <T extends JComponent> Class getJClass(T component) {
      Class<?> clazz = component.getClass();
      while (!clazz.getName().matches("javax.swing.J[^.]*$")) {
         clazz = clazz.getSuperclass();
      }
      return clazz;
   }
}