/**
 * Redistribution and use of this software and associated documentation ("Software"), with or
 * without modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain copyright statements and notices. Redistributions
 * must also contain a copy of this document.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of
 * conditions and the following disclaimer in the documentation and/or other materials provided with
 * the distribution.
 *
 * 3. The name "Exolab" must not be used to endorse or promote products derived from this Software
 * without prior written permission of Intalio, Inc. For written permission, please contact
 * info@exolab.org.
 *
 * 4. Products derived from this Software may not be called "Exolab" nor may "Exolab" appear in
 * their names without prior written permission of Intalio, Inc. Exolab is a registered trademark of
 * Intalio, Inc.
 *
 * 5. Due credit should be given to the Exolab Project (http://www.exolab.org/).
 *
 * THIS SOFTWARE IS PROVIDED BY INTALIO, INC. AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESSED OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL INTALIO, INC. OR ITS
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
 * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Copyright 1999-2005 (C) Intalio, Inc. All Rights Reserved.
 *
 * $Id$
 */

package org.exolab.castor.mapping;

import org.exolab.castor.mapping.loader.CollectionHandlers;
import java.util.Enumeration;

/**
 * An extended version of the FieldHandler interface which is used for making generic libraries of
 * FieldHandlers which can be used for more than one field or class, but have similar conversion
 * algorithms.
 *
 * @author <a href="kvisco-at-intalio.com">Keith Visco</a>
 * @version $Revision$ $Date: 2005-01-18 17:29:45 -0700 (Tue, 18 Jan 2005) $
 * @see FieldDescriptor
 * @see FieldHandler
 */
public abstract class GeneralizedFieldHandler extends AbstractFieldHandler {

  /**
   * Error message when a null FieldHandler is encountered
   */
  private static final String NULL_HANDLER_ERR =
      "A call to #setFieldHandler (with a non-null value) must be "
          + "made before calling this method.";

  /**
   * The actual FieldHandler to delegate to
   */
  private FieldHandler _handler = null;

  /**
   * The flag controlling automatic collection iteration during convertUponGet
   */
  private boolean _autoCollectionIteration = true;


  /**
   * Creates a new default GeneralizedFieldHandler. This method should be called by all extending
   * classes so that any important initialization code will be executed.
   */
  protected GeneralizedFieldHandler() {
    super();
    // -- currently nothing to do, but initialization
    // -- code may be needed in the future
  } // -- GeneralizedFieldHandler


  /**
   * This method is used to convert the value when the getValue method is called. The getValue
   * method will obtain the actual field value from given 'parent' object. This convert method is
   * then invoked with the field's value. The value returned from this method will be the actual
   * value returned by getValue method.
   *
   * @param value the object value to convert after performing a get operation
   * @return the converted value.
   */
  public abstract Object convertUponGet(Object value);

  /**
   * This method is used to convert the value when the setValue method is called. The setValue
   * method will call this method to obtain the converted value. The converted value will then be
   * used as the value to set for the field.
   *
   * @param value the object value to convert before performing a set operation
   * @return the converted value.
   */
  public abstract Object convertUponSet(Object value);

  /**
   * Returns the class type for the field that this GeneralizedFieldHandler converts to and from.
   * This should be the type that is used in the object model.
   *
   * @return the class type of of the field
   */
  public abstract Class getFieldType();

  /**
   * Sets the FieldHandler that this FieldHander delegates to. A call to this method must be made
   * with a non-null FieldHandler before this GeneralizedFieldHandler can be used.
   *
   * @param handler the FieldHandler to delegate to
   */
  public final void setFieldHandler(FieldHandler handler) {
    _handler = handler;
  } // -- setFieldHandler

  /**
   * Sets whether or not this GeneralizedFieldHandler should automatically iterate over the
   * collection returned by the target object and pass only the items (one by one) to the
   * convertUponGet method.
   * 
   * As of Castor 0.9.6 this is true by default.
   * 
   * @param autoCollectionIteration a boolean that when true indicates that this
   *        GeneralizedFieldHandler should automatically iterate over a collection and pass only
   *        collection items to the convertUponGet method.
   */
  public void setCollectionIteration(boolean autoCollectionIteration) {
    _autoCollectionIteration = autoCollectionIteration;
  } // -- setCollectionIteration

  // -----------------------------------------------/
  // - Methods inherited from AbstractFieldHandler -/
  // -----------------------------------------------/

  /**
   * Returns the value of the field from the object.
   *
   * @param object The object
   * @return The value of the field
   * @throws IllegalStateException The Java object has changed and is no longer supported by this
   *         handler, or the handler is not compatiable with the Java object
   */
  public final Object getValue(Object object) throws IllegalStateException {
    if (_handler == null) {
      throw new IllegalStateException(NULL_HANDLER_ERR);
    }

    Object value = _handler.getValue(object);
    if ((_autoCollectionIteration) && (value != null)) {

      if (value instanceof java.util.Enumeration) {
        return new GFHConverterEnumeration(this, (Enumeration) value);
      }
      // -- other collection type?
      if (CollectionHandlers.hasHandler(value.getClass())) {
        CollectionHandler colHandler = null;
        try {
          colHandler = CollectionHandlers.getHandler(value.getClass());
        } catch (MappingException mx) {
          throw new IllegalStateException(mx.getMessage());
        }
        return new GFHConverterEnumeration(this, colHandler.elements(value));
      }
    }

    return convertUponGet(value);
  } // -- getValue


  /**
   * Creates a new instance of the object described by this field.
   *
   * @param parent The object for which the field is created
   * @return A new instance of the field's value
   * @throws IllegalStateException This field is a simple type and cannot be instantiated
   */
  public Object newInstance(Object parent) throws IllegalStateException {
    if (_handler == null) {
      throw new IllegalStateException(NULL_HANDLER_ERR);
    }
    return _handler.newInstance(parent);
  }

  /**
   * Creates a new instance of the object described by this field.
   *
   * @param parent The object for which the field is created
   * @param args the set of constructor arguments
   * @return A new instance of the field's value
   * @throws IllegalStateException This field is a simple type and cannot be instantiated
   */
  public Object newInstance(Object parent, Object[] args) throws IllegalStateException {
    if (_handler instanceof ExtendedFieldHandler) {
      return ((ExtendedFieldHandler) _handler).newInstance(parent, args);
    }

    // -- backward compatibility: ignore arguments
    return newInstance(parent);
  }

  /**
   * Sets the value of the field to a default value.
   * <p>
   * Reference fields are set to null, primitive fields are set to their default value, collection
   * fields are emptied of all elements.
   *
   * @param object The object
   * @throws IllegalStateException The Java object has changed and is no longer supported by this
   *         handler, or the handler is not compatiable with the Java object
   */
  public final void resetValue(Object object)
      throws IllegalStateException, IllegalArgumentException {
    if (_handler == null) {
      throw new IllegalStateException(NULL_HANDLER_ERR);
    }
    _handler.resetValue(object);
  }

  /**
   * Sets the value of the field on the object.
   *
   * @param object The object.
   * @param value The new value.
   * @throws IllegalStateException The Java object has changed and is no longer supported by this
   *         handler, or the handler is not compatiable with the Java object.
   * @throws IllegalArgumentException The value passed is not of a supported type.
   */
  public final void setValue(Object object, Object value)
      throws IllegalStateException, IllegalArgumentException {
    if (_handler == null) {
      throw new IllegalStateException(NULL_HANDLER_ERR);
    }
    _handler.setValue(object, convertUponSet(value));
  }

  static class GFHConverterEnumeration implements Enumeration {

    Enumeration _enumeration = null;
    GeneralizedFieldHandler _handler = null;

    GFHConverterEnumeration(GeneralizedFieldHandler handler, Enumeration enumeration) {
      _enumeration = enumeration;
      _handler = handler;
    }

    public boolean hasMoreElements() {
      return _enumeration.hasMoreElements();
    }

    public Object nextElement() {
      Object value = _enumeration.nextElement();
      return _handler.convertUponGet(value);
    }


  }

}

