/* UIDefaults.java -- database for all settings and interface bindings.
   Copyright (C) 2002, 2004  Free Software Foundation, Inc.

This file is part of GNU Classpath.

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

GNU Classpath 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
General Public License for more details.

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

Linking this library statically or dynamically with other modules is
making a combined work based on this library.  Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.

As a special exception, the copyright holders of this library give you
permission to link this library with independent modules to produce an
executable, regardless of the license terms of these independent
modules, and to copy and distribute the resulting executable under
terms of your choice, provided that you also meet, for each linked
independent module, the terms and conditions of the license of that
module.  An independent module is a module which is not derived from
or based on this library.  If you modify this library, you may extend
this exception to your version of the library, but you are not
obligated to do so.  If you do not wish to do so, delete this
exception statement from your version. */

package javax.swing;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Insets;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.lang.reflect.Method;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.ListIterator;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import javax.swing.border.Border;
import javax.swing.plaf.ComponentUI;

/**
 * UIDefaults is a database where all settings and interface bindings are
 * stored into. An PLAF implementation fills one of these (see for example
 * plaf/basic/BasicLookAndFeel.java) with "ButtonUI" -> new BasicButtonUI().
 *
 * @author Ronald Veldema (rveldema@cs.vu.nl)
 */
public class UIDefaults extends Hashtable
{
  private LinkedList bundles;
  private Locale defaultLocale;
  private PropertyChangeSupport propertyChangeSupport;

  public interface ActiveValue
  {
    Object createValue(UIDefaults table);
  }

  public static class LazyInputMap implements LazyValue
  {
    Object[] bind;
    public LazyInputMap(Object[] bindings)
    {
      bind = bindings;
    }
    public Object createValue(UIDefaults table)
    {
      InputMap im = new InputMap ();
      for (int i = 0; 2*i+1 < bind.length; ++i)
        {
          im.put (KeyStroke.getKeyStroke ((String) bind[2*i]),
                  bind[2*i+1]);
        }
      return im;
    }
  }

  public interface LazyValue
  {
    Object createValue(UIDefaults table);
  }

  public static class ProxyLazyValue implements LazyValue
  {
    LazyValue inner;
    public ProxyLazyValue(String s)
    {
      final String className = s;
      inner = new LazyValue ()
        { 
          public Object createValue (UIDefaults table) 
          {
            try
              {
                return Class
                  .forName(className)
                  .getConstructor(new Class[] {})
                  .newInstance(new Object[] {});
              }
            catch (Exception e)
              {
                return null;
              }
          }
        };
    }

    public ProxyLazyValue(String c, String m)
    {
      final String className = c;
      final String methodName = m;
      inner = new LazyValue ()
        { 
          public Object createValue (UIDefaults table) 
          {
            try 
              {                
                return Class
                  .forName (className)
                  .getMethod (methodName, new Class[] {})
                  .invoke (null, new Object[] {});
              }
            catch (Exception e)
              {
                return null;
              }
          }
        };
    }
    
    public ProxyLazyValue(String c, Object[] os)
    {
      final String className = c;
      final Object[] objs = os;
      final Class[] clss = new Class[objs.length];
      for (int i = 0; i < objs.length; ++i)
        {
          clss[i] = objs[i].getClass();
        }      
      inner = new LazyValue()
        { 
          public Object createValue(UIDefaults table) 
          {            
            try
              {
                return Class
                  .forName(className)
                  .getConstructor(clss)
                  .newInstance(objs);
	      }
            catch (Exception e)
	      {
                return null;
              }
          }
        };
    }

    public ProxyLazyValue(String c, String m, Object[] os)
    {
      final String className = c;
      final String methodName = m;
      final Object[] objs = os;
      final Class[] clss = new Class[objs.length];
      for (int i = 0; i < objs.length; ++i)
	{
          clss[i] = objs[i].getClass();
	}
      inner = new LazyValue()
        { 
	  public Object createValue(UIDefaults table)
	  {
            try 
              {
                return Class
                  .forName(className)
                  .getMethod(methodName, clss)
                  .invoke(null, objs);
              }
            catch (Exception e)
              {
                return null;
              }
          }
        };
    }
    
    public Object createValue(UIDefaults table)
    {
      return inner.createValue(table);
    }
  }

  private static final long serialVersionUID = 7341222528856548117L;

  public UIDefaults()
  {
    bundles = new LinkedList();
    defaultLocale = Locale.getDefault();
    propertyChangeSupport = new PropertyChangeSupport(this);
  }

  public UIDefaults(Object[] entries)
  {
    this();
    
    for (int i = 0; (2 * i + 1) < entries.length; ++i)
      put(entries[2 * i], entries[2 * i + 1]);
  }

  public Object get(Object key)
  {
    return this.get(key, getDefaultLocale());
  }

  public Object get(Object key, Locale loc)
  {
    Object obj = null;

    if (super.containsKey(key))
      {
        obj = super.get(key);
      }
    else if (key instanceof String)
      {
        String keyString = (String) key;
        ListIterator i = bundles.listIterator(0);
        while (i.hasNext())
	  {
            String bundle_name = (String) i.next();
            ResourceBundle res =
              ResourceBundle.getBundle(bundle_name, loc);
            if (res != null)
              {
                try 
                  {                    
                    obj = res.getObject(keyString);
                    break;
                  }
                catch (MissingResourceException me)
                  {
                    // continue, this bundle has no such key
                  }
              }
          }
      }

    // now we've found the object, resolve it.
    // nb: LazyValues aren't supported in resource bundles, so it's correct
    // to insert their results in the locale-less hashtable.

    if (obj == null)
      return null;

    if (obj instanceof LazyValue)
      {
        Object resolved = ((LazyValue) obj).createValue(this);
        super.remove(key);
        super.put(key, resolved);
        return resolved;
      }
    else if (obj instanceof ActiveValue)
      {
        return ((ActiveValue) obj).createValue(this);
      }    

    return obj;
  }

  public Object put(Object key, Object value)
  {
    Object old = super.put(key, value);
    if (key instanceof String && old != value)
      firePropertyChange((String) key, old, value);
    return old;
  }

  public void putDefaults(Object[] entries)
  {
    for (int i = 0; (2 * i + 1) < entries.length; ++i)
  {
        super.put(entries[2 * i], entries[2 * i + 1]);
      }
    firePropertyChange("UIDefaults", null, null);
  }

  public Font getFont(Object key)
  {
    Object o = get(key);
    return o instanceof Font ? (Font) o : null;
  }

  public Font getFont(Object key, Locale l)
  {
    Object o = get(key, l);
    return o instanceof Font ? (Font) o : null;
  }

  public Color getColor(Object key)
  {
    Object o = get(key);
    return o instanceof Color ? (Color) o : null;
  }

  public Color getColor(Object key, Locale l)
  {
    Object o = get(key, l);
    return o instanceof Color ? (Color) o : null;
  }

  public Icon getIcon(Object key)
  {
    Object o = get(key);
    return o instanceof Icon ? (Icon) o : null;
  }

  public Icon getIcon(Object key, Locale l)
  {
    Object o = get(key, l);
    return o instanceof Icon ? (Icon) o : null;
  }

  public Border getBorder(Object key)
  {
    Object o = get(key);
    return o instanceof Border ? (Border) o : null;
  }

  public Border getBorder(Object key, Locale l)
  {
    Object o = get(key, l);
    return o instanceof Border ? (Border) o : null;
  }

  public String getString(Object key)
  {
    Object o = get(key);
    return o instanceof String ? (String) o : null;
  }

  public String getString(Object key, Locale l)
  {
    Object o = get(key, l);
    return o instanceof String ? (String) o : null;
  }

  public int getInt(Object key)
  {
    Object o = get(key);
    return o instanceof Integer ? ((Integer) o).intValue() : 0;
  }

  public int getInt(Object key, Locale l)
  {
    Object o = get(key, l);
    return o instanceof Integer ? ((Integer) o).intValue() : 0;
  }

  public boolean getBoolean(Object key)
  {
    return Boolean.TRUE.equals(get(key));
  }

  public boolean getBoolean(Object key, Locale l)
  {
    return Boolean.TRUE.equals(get(key, l));
  }

  public Insets getInsets(Object key) 
  {
    Object o = get(key);
    return o instanceof Insets ? (Insets) o : null;
  }

  public Insets getInsets(Object key, Locale l) 
  {
    Object o = get(key, l);
    return o instanceof Insets ? (Insets) o : null;
  }

  public Dimension getDimension(Object key) 
  {
    Object o = get(key);
    return o instanceof Dimension ? (Dimension) o : null;
  }

  public Dimension getDimension(Object key, Locale l) 
  {
    Object o = get(key, l);
    return o instanceof Dimension ? (Dimension) o : null;
  }

  public Class getUIClass(String id, ClassLoader loader)
  {
    String className = (String) get (id);
    if (className == null)
      return null;
    try 
      {
        if (loader != null)
          return loader.loadClass (className);    
        return Class.forName (className);
      }
    catch (Exception e)
      {
        return null;
      }
  }

  public Class getUIClass(String id)
  {
    return getUIClass (id, null);
  }

  protected void getUIError(String msg)
  {
    System.err.println ("UIDefaults.getUIError: " + msg);
  }

  public ComponentUI getUI(JComponent target)
  {
    String classId = target.getUIClassID ();
    Class cls = getUIClass (classId);
    if (cls == null)
      {
        getUIError ("failed to locate UI class:" + classId);
        return null;
      }

    Method factory;

    try 
      {
        factory = cls.getMethod ("createUI", new Class[] { JComponent.class } );
      }
    catch (NoSuchMethodException nme)
      {
        getUIError ("failed to locate createUI method on " + cls.toString ());
        return null;
      }

    try
      {
        return (ComponentUI) factory.invoke (null, new Object[] { target });
      }
    catch (java.lang.reflect.InvocationTargetException ite)
      {
        getUIError ("InvocationTargetException ("+ ite.getTargetException() 
		    +") calling createUI(...) on " + cls.toString ());
        return null;        
      }
    catch (Exception e)
      {
        getUIError ("exception calling createUI(...) on " + cls.toString ());
        return null;        
      }
  }

  public void addPropertyChangeListener(PropertyChangeListener listener)
  {
    propertyChangeSupport.addPropertyChangeListener(listener);
  }

  void removePropertyChangeListener(PropertyChangeListener listener)
  {
    propertyChangeSupport.removePropertyChangeListener(listener);
  }

  public PropertyChangeListener[] getPropertyChangeListeners()
  {
    return propertyChangeSupport.getPropertyChangeListeners();
  }

  protected void firePropertyChange(String property,
				    Object oldValue, Object newValue)
  {
    propertyChangeSupport.firePropertyChange(property, oldValue, newValue);
  }

  public void addResourceBundle(String name)
  {
    bundles.addFirst(name);
  }

  public void removeResourceBundle(String name)
  {
    bundles.remove(name);
  }

  public void setDefaultLocale(Locale loc)
  {
    defaultLocale = loc;
  }

  public Locale getDefaultLocale()
  {
    return defaultLocale;
  }
}
