/**
 * GUI Commands
 * Copyright 2004 Andrew Pietsch
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * $Id: BuilderRegistry.java,v 1.6 2005/05/28 23:11:59 pietschy Exp $
 */

package org.pietschy.command;

import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;

/**
 * This class provides an intelligent map for finding the best builder for a particular Command
 * subclass.  If an exact match isn't found, the registry searches through all the entries to
 * find the builder for the nearest ancestor.
 */
class
BuilderRegistry
{
   static final String _ID_ = "$Id: BuilderRegistry.java,v 1.6 2005/05/28 23:11:59 pietschy Exp $";

   HashMap builders = new HashMap();
   HashMap ancestors = new HashMap();

   private CommandManager commandManager;

   public BuilderRegistry(CommandManager manager)
   {
      this.commandManager = manager;
   }

   /**
    * Registers the specified builder for instances of the specified class.  Subclasses of
    * the specified class will also use this builder if they don't have a specified builder
    * registered.
    * @param clazz the command class.
    * @param builder the builder for the specified class.
    */
   public void
   register(Class clazz, AbstractCommandBuilder builder)
   {
      if (!Command.class.isAssignableFrom(clazz))
         throw new IllegalArgumentException("Class " + clazz.getName() +
                                            " must extend " + Command.class.getName());
      if (builder == null)
         throw new NullPointerException("builder is null");

      builder.setCommandManager(commandManager);
      builders.put(clazz, builder);
      ancestors.clear();
   }

   /**
    * Gets the best builder for the specified command class.  This searches through all the
    * registered builders to find the most specific instance.
    */
   public AbstractCommandBuilder
   get(Class clazz)
   {
      AbstractCommandBuilder configurator = (AbstractCommandBuilder) builders.get(clazz);
      if (configurator == null)
      {
         configurator = (AbstractCommandBuilder) ancestors.get(clazz);

         if (configurator == null)
         {
            Class nearestAncestor = findNearestAncestor(clazz, builders.keySet());
            configurator = (AbstractCommandBuilder) builders.get(nearestAncestor);
            ancestors.put(clazz, configurator);
         }
      }

      return configurator;
   }

   private Class
   findNearestAncestor(Class clazz, Collection classes)
   {
      int bestLineage = Integer.MAX_VALUE;
      Class bestClass = null;
      for (Iterator iterator = classes.iterator(); iterator.hasNext();)
      {
         Class c = (Class) iterator.next();

         if (c.isAssignableFrom(clazz))
         {
            int lineage = getLineage(c, clazz);
            if (lineage < bestLineage)
            {
               bestClass = c;
               bestLineage = lineage;
            }
         }
      }

      return bestClass;
   }

   private int
   getLineage(Class a, Class b)
   {
      if (!a.isAssignableFrom(b))
         throw new IllegalArgumentException("class b must be a subclass of a");

      int lineage = 0;
      while (b != a)
      {
         b = b.getSuperclass();
         lineage++;
      }

      return lineage;
   }
}
