package org.exolab.castor.mapping.loader;

import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Vector;

import org.exolab.castor.mapping.ClassDescriptor;
import org.exolab.castor.mapping.MappingException;
import org.exolab.castor.mapping.MappingLoader;

public abstract class AbstractMappingLoader2 implements MappingLoader {

  /** The class loader to use. */
  private ClassLoader _loader;

  /** A flag indicating whether or not mappings can be redefined. */
  private boolean _allowRedefinitions = false;

  /** Has loadMapping been called? */
  private boolean _loaded = false;

  /**
   * All class descriptors in the original order.
   */
  private final List<ClassDescriptor> _descriptors = new Vector<>();

  /**
   * All class descriptors added so far, keyed by class name.
   */
  private final Map<String, ClassDescriptor> _descriptorsByClassname =
      new Hashtable<String, ClassDescriptor>();

  public AbstractMappingLoader2(final ClassLoader loader) {
    setClassLoader(loader);
  }

  public final void clear() {
    _allowRedefinitions = false;
    _loaded = false;
    _descriptors.clear();
    _descriptorsByClassname.clear();
  }

  /**
   * @see org.exolab.castor.mapping.MappingLoader#setClassLoader(java.lang.ClassLoader)
   *      {@inheritDoc}
   */
  public final void setClassLoader(final ClassLoader loader) {
    if (loader == null) {
      _loader = getClass().getClassLoader();
    } else {
      _loader = loader;
    }
  }

  /**
   * @see org.exolab.castor.mapping.MappingLoader#getClassLoader() {@inheritDoc}
   */
  public final ClassLoader getClassLoader() {
    return _loader;
  }

  /**
   * Enables or disables the ability to allow the redefinition of class mappings.
   * 
   * @param allow A boolean that when true enables redefinitions.
   */
  public final void setAllowRedefinitions(boolean allow) {
    _allowRedefinitions = allow;
  }

  /**
   * Is the ability to allow redefinitions enabled or disabled?
   * 
   * @return A boolean that when true enables redefinitions.
   */
  public final boolean isAllowRedefinition() {
    return _allowRedefinitions;
  }

  /**
   * Adds a class descriptor. Will throw a mapping exception if a descriptor for this class already
   * exists.
   *
   * @param descriptor The descriptor to add.
   * @throws MappingException A descriptor for this class already exists.
   */
  protected final void addDescriptor(final ClassDescriptor descriptor) throws MappingException {
    String classname = descriptor.getJavaClass().getName();
    if (_descriptorsByClassname.containsKey(classname)) {
      if (!isAllowRedefinition()) {
        throw new MappingException("mapping.duplicateDescriptors", classname);
      }
      for (Iterator<ClassDescriptor> iterator = _descriptors.iterator(); iterator.hasNext();) {
        ClassDescriptor d = iterator.next();
        if (classname.equals(d.getJavaClass().getName())) {
          iterator.remove();
        }
      }
      _descriptors.add(descriptor);
    } else {
      _descriptors.add(descriptor);
    }

    // -- if we make it here...add class
    _descriptorsByClassname.put(classname, descriptor);
  }

  /**
   * @see org.exolab.castor.mapping.MappingLoader#getDescriptor(java.lang.String) {@inheritDoc}
   */
  public final ClassDescriptor getDescriptor(final String classname) {
    if (classname == null) {
      return null;
    }
    return _descriptorsByClassname.get(classname);
  }

  // /**
  // * @see org.exolab.castor.mapping.MappingLoader#descriptorIterator()
  // * {@inheritDoc}
  // */
  // public final Iterator<ClassDescriptor> descriptorIterator() {
  // return _descriptors.iterator();
  // }

  public final List<ClassDescriptor> getDescriptors() {
    return _descriptors;
  }

  /**
   * Return if mapping should be loaded with this MappingLoader instance or if another mapping have
   * been loaded previously. If no mapping have been loaded previously then prevent any other
   * mapping to be loaded later on.
   * 
   * @return <code>true</code> if mapping should be loaded, <code>false</code> otherwise.
   */
  protected final boolean loadMapping() {
    if (_loaded) {
      return false;
    }
    _loaded = true;
    return true;
  }

}
