/**
 * Copyright (c) 2002-2006 IBM Corporation and others.
 * All rights reserved.   This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v2.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v20.html
 *
 * Contributors:
 *   IBM - Initial API and implementation
 */
package org.eclipse.emf.mapping.domain;


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

import org.eclipse.emf.common.command.Command;
import org.eclipse.emf.common.command.CommandStack;
import org.eclipse.emf.common.command.UnexecutableCommand;
import org.eclipse.emf.common.notify.AdapterFactory;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.notify.NotificationWrapper;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.ENamedElement;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.ETypedElement;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.edit.command.AddCommand;
import org.eclipse.emf.edit.command.CommandParameter;
import org.eclipse.emf.edit.command.CopyToClipboardCommand;
import org.eclipse.emf.edit.command.CreateCopyCommand;
import org.eclipse.emf.edit.command.DragAndDropCommand;
import org.eclipse.emf.edit.command.InitializeCopyCommand;
import org.eclipse.emf.edit.command.MoveCommand;
import org.eclipse.emf.edit.command.OverrideableCommand;
import org.eclipse.emf.edit.command.PasteFromClipboardCommand;
import org.eclipse.emf.edit.command.RemoveCommand;
import org.eclipse.emf.edit.command.ReplaceCommand;
import org.eclipse.emf.edit.command.SetCommand;
import org.eclipse.emf.edit.domain.AdapterFactoryEditingDomain;
import org.eclipse.emf.edit.domain.EditingDomain;
import org.eclipse.emf.edit.domain.IEditingDomainProvider;
import org.eclipse.emf.edit.provider.ComposedAdapterFactory;
import org.eclipse.emf.edit.provider.IItemLabelProvider;
import org.eclipse.emf.edit.provider.ITreeItemContentProvider;
import org.eclipse.emf.mapping.Mapping;
import org.eclipse.emf.mapping.MappingPlugin;
import org.eclipse.emf.mapping.MappingRoot;
import org.eclipse.emf.mapping.command.AddOverrideCommand;
import org.eclipse.emf.mapping.command.CopyToClipboardOverrideCommand;
import org.eclipse.emf.mapping.command.CreateCopyOverrideCommand;
import org.eclipse.emf.mapping.command.CreateMappingCommand;
import org.eclipse.emf.mapping.command.DragAndDropOverrideCommand;
import org.eclipse.emf.mapping.command.InitializeCopyOverrideCommand;
import org.eclipse.emf.mapping.command.PasteFromClipboardOverrideCommand;
import org.eclipse.emf.mapping.command.PersistentCommandStack;
import org.eclipse.emf.mapping.command.RemoveOverrideCommand;
import org.eclipse.emf.mapping.command.SetOverrideCommand;
import org.eclipse.emf.mapping.provider.MappedObjectItemProvider;


/**
 * This class implements {@link MappingDomain} interface using two strategies. 
 * The primary strategy is to delegate to an adapter that is produced by a {@link ComposedAdapterFactory} that comprises 
 * an adapter factory for the mapping model, 
 * an adapter factory for the top domain,
 * and an adapter factory for the bottom domain (which may be the same as the one for the top domain).
 * The secondary strategy is to use table lookup, 
 * which provides an easy way to supply the input/ouput type correspondence information from a plugin configuration file, 
 * when the top and bottom domains are of different type.
 */
public class AdapterFactoryMappingDomain extends AdapterFactoryEditingDomain implements MappingDomain
{
  public static final int LABEL_UPPER = 1;
  public static final int LABEL_LOWER = 2;
  public static final int LABEL_MIXED = 3;
  public static final char LABEL_NO_SEPARATOR = '\0';

  protected HashMap<String, String> topToBottomTypeTable = new HashMap<String, String>();
  protected HashMap<String, String> bottomToTopTypeTable = new HashMap<String, String>();

  protected char topLabelSeparator = LABEL_NO_SEPARATOR;
  protected char bottomLabelSeparator = LABEL_NO_SEPARATOR;

  protected int topLabelCase = LABEL_MIXED;
  protected int bottomLabelCase = LABEL_MIXED;

  protected List<String> topLabelLongForms = new ArrayList<String>();
  protected List<String> bottomLabelLongForms = new ArrayList<String>();

  protected List<String> topLabelShortForms = new ArrayList<String>();
  protected List<String> bottomLabelShortForms = new ArrayList<String>();

  protected char packageTypeSeparator = '|';

  protected MappingRoot mappingRoot;
  protected boolean isSameEditingDomainAdapterFactory;
  protected PersistentCommandStack persistentCommandStack;

  protected int mappingEnablementFlags = 0;

  public AdapterFactoryMappingDomain
    (AdapterFactory mappingDomainAdapterFactory,
     AdapterFactory editingDomainAdapterFactory,
     CommandStack commandStack) 
  {
    this(mappingDomainAdapterFactory, editingDomainAdapterFactory, editingDomainAdapterFactory, commandStack);
  }

  public AdapterFactoryMappingDomain
    (AdapterFactory mappingDomainAdapterFactory,
     AdapterFactory editingDomainAdapterFactory,
     CommandStack commandStack,
     ResourceSet resourceSet) 
  {
    this(mappingDomainAdapterFactory, editingDomainAdapterFactory, editingDomainAdapterFactory, commandStack, resourceSet);
  }

  public AdapterFactoryMappingDomain
    (AdapterFactory mappingDomainAdapterFactory, 
     AdapterFactory topDomainAdapterFactory, 
     AdapterFactory bottomDomainAdapterFactory,
     CommandStack commandStack)
  {
    super(null, commandStack);

    adapterFactory = createComposedAdapterFactory(mappingDomainAdapterFactory, topDomainAdapterFactory, bottomDomainAdapterFactory);

    isSameEditingDomainAdapterFactory = (topDomainAdapterFactory == bottomDomainAdapterFactory);

    if (commandStack instanceof PersistentCommandStack)
    {
      persistentCommandStack = (PersistentCommandStack)commandStack;
    }
  }

  public AdapterFactoryMappingDomain
    (AdapterFactory mappingDomainAdapterFactory, 
     AdapterFactory topDomainAdapterFactory, 
     AdapterFactory bottomDomainAdapterFactory,
     CommandStack commandStack,
     ResourceSet resourceSet)
  {
    super(null, commandStack, resourceSet);

    adapterFactory = createComposedAdapterFactory(mappingDomainAdapterFactory, topDomainAdapterFactory, bottomDomainAdapterFactory);

    isSameEditingDomainAdapterFactory = (topDomainAdapterFactory == bottomDomainAdapterFactory);

    if (commandStack instanceof PersistentCommandStack)
    {
      persistentCommandStack = (PersistentCommandStack)commandStack;
    }
  }

  protected class LabelUpdatePropagatingComposedAdapterFactory extends ComposedAdapterFactory
  {
    public LabelUpdatePropagatingComposedAdapterFactory(AdapterFactory adapterFactory)
    {
      super(adapterFactory);
    }

    //public void fireNotifyChanged(Object object, int eventType, Object feature, Object oldValue, Object newValue, int index)
    @Override
    public void fireNotifyChanged(Notification note)
    {
      super.fireNotifyChanged(note);
      // if (!viewerUpdate.isStructuralChange())
      {
        if (parentAdapterFactory != null && getMappingRoot() != null && note.getNotifier() != null)
        {
          for (Mapping mapping : getMappingRoot().getMappings(note.getNotifier()))
          {
            parentAdapterFactory.fireNotifyChanged(new NotificationWrapper(mapping, note));

            //FB Won't compile with VAJ or NO_JIKES 
            //FB Object adapter = adapterFactory.adapt(mapping);
            ITreeItemContentProvider treeItemContentProvider = 
              (ITreeItemContentProvider)getAdapterFactory().adapt(mapping, ITreeItemContentProvider.class);

            if (treeItemContentProvider != null)
            {
              for (Iterator<?> children = treeItemContentProvider.getChildren(mapping).iterator(); children.hasNext(); )
              {
                Object child = children.next();
                if (child instanceof MappedObjectItemProvider &&
                      ((MappedObjectItemProvider)child).getMappedObject() == note.getNotifier())
                {
                  parentAdapterFactory.fireNotifyChanged(new NotificationWrapper(child, note));
                }
              }
            }
          }
        }
      }
    }
  }

  public static class EditingDomainProvidingComposedAdapterFactory extends ComposedAdapterFactory implements IEditingDomainProvider
  {
    protected EditingDomain editingDomain;

    /**
     */
    public EditingDomainProvidingComposedAdapterFactory(AdapterFactory adapterFactory, EditingDomain editingDomain)
    {
      super(adapterFactory);
      this.editingDomain = editingDomain;
      addAdapterFactory(adapterFactory);
    }

    public EditingDomainProvidingComposedAdapterFactory(AdapterFactory [] adapterFactories, EditingDomain editingDomain)
    {
      super(adapterFactories);
      this.editingDomain = editingDomain;
    }

    public EditingDomainProvidingComposedAdapterFactory(Collection<? extends AdapterFactory> adapterFactories, EditingDomain editingDomain)
    {
      super(adapterFactories);
      this.editingDomain = editingDomain;
    }

    public EditingDomain getEditingDomain()
    {
      return editingDomain;
    }
  }

  protected ComposedAdapterFactory createComposedAdapterFactory
    (AdapterFactory mappingDomainAdapterFactory,
     AdapterFactory topDomainAdapterFactory,
     AdapterFactory bottomDomainAdapterFactory)
  {
    AdapterFactory[] adapterFactories;

    if (topDomainAdapterFactory == bottomDomainAdapterFactory)
    {
      adapterFactories = 
        new AdapterFactory[] 
        {
          mappingDomainAdapterFactory, 
          new LabelUpdatePropagatingComposedAdapterFactory(topDomainAdapterFactory)
        };
    }
    else
    {
      adapterFactories = 
        new AdapterFactory[] 
        {
          mappingDomainAdapterFactory, 
          new LabelUpdatePropagatingComposedAdapterFactory(topDomainAdapterFactory), 
          new LabelUpdatePropagatingComposedAdapterFactory(bottomDomainAdapterFactory)
        };
    }

    return createComposedAdapterFactory(adapterFactories);
  }

  protected ComposedAdapterFactory createComposedAdapterFactory(AdapterFactory[] adapterFactories)
  {
    return new EditingDomainProvidingComposedAdapterFactory(adapterFactories, this);
  }
  
  public String getOutputName(String inputName) 
  {
    if (isSameEditingDomainAdapterFactory)
    {
      return inputName;
    }
    else if (mappingRoot.isTopToBottom())
    {
      return 
        convertName
          (inputName, 
           topLabelSeparator, 
           topLabelCase, 
           topLabelShortForms, 
           topLabelLongForms,
           bottomLabelSeparator, 
           bottomLabelCase, 
           bottomLabelShortForms, 
           bottomLabelLongForms);
    }
    else
    {
      return 
        convertName
          (inputName, 
           bottomLabelSeparator, 
           bottomLabelCase, 
           bottomLabelShortForms, 
           bottomLabelLongForms,
           topLabelSeparator, 
           topLabelCase, 
           topLabelShortForms, 
           topLabelLongForms);
    }
  }

  public List<String> parseOutputName(String outputName) 
  {
    if (mappingRoot.isTopToBottom())
    {
      return parseName(outputName, bottomLabelSeparator);
    }
    else
    {
      return parseName(outputName, topLabelSeparator);
    }
  }

  public List<String> parseInputName(String inputName) 
  {
    if (mappingRoot.isTopToBottom())
    {
      return parseName(inputName, topLabelSeparator);
    }
    else
    {
      return parseName(inputName, bottomLabelSeparator);
    }
  }

  public String getName(Object object)
  {
    // If eCore, return the ENamedElement name
    //
    if (object instanceof ENamedElement)
    {
      return ((ENamedElement)object).getName();
    }

    // If there is an adapter of the correct type, return the item label
    //
    IItemLabelProvider itemLabelProvider = (IItemLabelProvider)adapterFactory.adapt(object, IItemLabelProvider.class);
    return
      itemLabelProvider != null ?
       itemLabelProvider.getText(object) :
       null;
  }

  public void setName(Object object, String name)
  {
    // If eCore, set the ENamedElement name
    //
    if (object instanceof ENamedElement)
    {
      ((ENamedElement)object).setName(name);
    }
  }

  public EObject getOutputMetaObject(EObject inputMetaObject) 
  {
    if (isSameEditingDomainAdapterFactory)
    {
      return inputMetaObject;
    }
    else
    {
      return getCorrespondingType(inputMetaObject, mappingRoot.isTopToBottom() ? topToBottomTypeTable : bottomToTopTypeTable);
    }
  } 

  public MappingRoot getMappingRoot() 
  {
    return mappingRoot;
  }      
   
  public void setMappingRoot(MappingRoot mappingRoot) 
  {
    if (this.mappingRoot != mappingRoot)
    {
      this.mappingRoot = mappingRoot;
    }
  } 

  /**
   * This default implementation returns null or the {@link ETypedElement} if the object is an {@link ETypedElement}.
   */
  public Object getTypeClassifier(Object mappedObject)
  {
    if (mappedObject instanceof ETypedElement)
    {
      return ((ETypedElement)mappedObject).getEType();
    }
    return null;
  }

  /**
   * This default implementation sets the {@link ETypedElement} if the object is an {@link ETypedElement}.
   */
  public void setTypeClassifier(Object mappedObject, Object typeClassifier)
  {
    if (mappedObject instanceof ETypedElement)
    {
      ((ETypedElement)mappedObject).setEType((EClassifier)typeClassifier);
    }
  }

  public Object getOutputTypeClassifier(Object inputType)
  {
    Object outputType = null;
    MappingRoot typeMappingRoot = mappingRoot.getTypeMappingRoot();
    if (typeMappingRoot != null)
    {
      Collection<? extends Mapping> mappings = typeMappingRoot.getMappings(inputType);
      if (mappings.size() == 1)
      {
        Mapping typeMapping = mappings.iterator().next();
        outputType = typeMapping.getOutputs().iterator().next();
      }
    }
    return outputType;
  }

  public int getMappingEnablementFlags()
  {
    return mappingEnablementFlags;
  }

  public void setMappingEnablementFlags(int mappingEnablementFlags)
  {
    this.mappingEnablementFlags = mappingEnablementFlags;
  }

  /**
   * This is called for every command created by the domain.
   */
  public void handleCreateCommand(Class<? extends Command> commandClass, CommandParameter commandParameter, Command command)
  {
    if (persistentCommandStack != null)
    {
      persistentCommandStack.handleCreateCommand(commandClass, commandParameter, command);
    }
  }

  @Override
  public Command createCommand(Class<? extends Command> commandClass, CommandParameter commandParameter)
  {
    // This turns a featureless set command into an operation to create a mapping.
    // This is invoked during drag and drop.
    //
    if (commandClass == SetCommand.class && 
          !(commandParameter.getOwner() instanceof Mapping) &&
          !(commandParameter.getOwner() instanceof MappedObjectItemProvider) &&
          commandParameter.getFeature() == null && 
          commandParameter.getValue() instanceof Collection<?>)
    {
      boolean inputToOutput = mappingRoot.isOutputObject(commandParameter.getOwner());
      boolean okay = true;
      for (Object object : (Collection<?>)commandParameter.getValue())
      {
        if (mappingRoot.isInputObject(object) != inputToOutput)
        {
          okay = false;
        }
      }
      if (okay)
      {
        Collection<Object> mappedObjects = new ArrayList<Object>((Collection<?>)commandParameter.getValue());
        mappedObjects.add(commandParameter.getOwner());
        return CreateMappingCommand.create(this, mappedObjects);
      }
    }  
    else if (commandClass == RemoveCommand.class && commandParameter.getOwner() == null)
    {
      // If this is a RemoveCommand for a root input object, direct it to the MappingRoot.
      //
      Collection<?> collection = commandParameter.getCollection();
      if (mappingRoot.getMappedObjects().containsAll(collection))
      {
        commandParameter.setOwner(mappingRoot);
      }
    }

    if ((mappingRoot.isInputObject(commandParameter.getOwner()) ||
                mappingRoot.isOutputReadOnly() && mappingRoot.isOutputObject(commandParameter.getOwner())) && 
               (commandClass == AddCommand.class ||
                  commandClass == MoveCommand.class ||
                  commandClass == RemoveCommand.class ||
                  commandClass == ReplaceCommand.class ||
                  commandClass == SetCommand.class))
    {
      return UnexecutableCommand.INSTANCE;
    }
    else
    {
      Command result = super.createCommand(commandClass, commandParameter);
      handleCreateCommand(commandClass, commandParameter, result);
      return result;
    }
  }

  @Override
  public Command createOverrideCommand(OverrideableCommand command)
  {
    if (command instanceof AddCommand)
    {
      AddCommand addCommand = (AddCommand)command;
      return createAddOverrideCommand(addCommand);
    }
    else if (command instanceof RemoveCommand)
    {
      RemoveCommand removeCommand = (RemoveCommand)command;
      return createRemoveOverrideCommand(removeCommand);
    }
    else if (command instanceof SetCommand)
    {
      SetCommand setCommand = (SetCommand)command;
      return createSetOverrideCommand(setCommand);
    }
    else if (command instanceof ReplaceCommand)
    {
      ReplaceCommand replaceCommand = (ReplaceCommand)command;
      return createReplaceOverrideCommand(replaceCommand);
    }
    else if (command instanceof MoveCommand)
    {
      MoveCommand moveCommand = (MoveCommand)command;
      return createMoveOverrideCommand(moveCommand);
    }
    else if (command instanceof CreateCopyCommand)
    {
      CreateCopyCommand createCopyCommand = (CreateCopyCommand)command;
      return createCreateCopyOverrideCommand(createCopyCommand);
    }
    else if (command instanceof InitializeCopyCommand)
    {
      InitializeCopyCommand initializeCopyCommand = (InitializeCopyCommand)command;
      return createInitializeCopyOverrideCommand(initializeCopyCommand);
    }
    else if (command instanceof CopyToClipboardCommand)
    {
      CopyToClipboardCommand copyToClipboardCommand = (CopyToClipboardCommand)command;
      return createCopyToClipboardOverrideCommand(copyToClipboardCommand);
    }
    else if (command instanceof PasteFromClipboardCommand)
    {
      PasteFromClipboardCommand pasteFromClipboardCommand = (PasteFromClipboardCommand)command;
      return createPasteFromClipboardOverrideCommand(pasteFromClipboardCommand);
    }
    else if (command instanceof DragAndDropCommand)
    {
      DragAndDropCommand dragAndDropCommand = (DragAndDropCommand)command;
      return createDragAndDropOverrideCommand(dragAndDropCommand);
    }
    else
    {
      return null;
    }
  }

  protected Command createAddOverrideCommand(AddCommand addCommand)
  {
    if (!(addCommand.getOwner() instanceof Mapping))
    {
      return new AddOverrideCommand(this, addCommand);
    }

    return null;
  }

  protected Command createRemoveOverrideCommand(RemoveCommand removeCommand)
  {
    if (!(removeCommand.getOwner() instanceof Mapping))
    {
      return new RemoveOverrideCommand(this, removeCommand);
    }

    return null;
  }

  protected Command createSetOverrideCommand(SetCommand setCommand)
  {
    EStructuralFeature feature = setCommand.getFeature();
    return 
      feature instanceof EReference && ((EReference)feature).isContainment() && mappingRoot.isOutputObject(setCommand.getOwner()) ?
        new SetOverrideCommand(this, setCommand) :
        null;
  }

  protected Command createMoveOverrideCommand(MoveCommand moveCommand)
  {
    return null;
  }

  protected Command createReplaceOverrideCommand(ReplaceCommand replaceCommand)
  {
    return null;
  }

  protected Command createCreateCopyOverrideCommand(CreateCopyCommand createCopyCommand)
  {
    if (mappingRoot.isInputObject(createCopyCommand.getOwner()))
    {
      return new CreateCopyOverrideCommand(this, createCopyCommand);
    }

    return null;
  }

  protected Command createInitializeCopyOverrideCommand(InitializeCopyCommand initializeCopyCommand)
  {
    // If the owner and the copy are of the same type, we can proceed with the command, i.e. no override.
    // If not, we will skip this command by returning a do-nothing override.
    //
    if (!isSameEditingDomainAdapterFactory)
    {
      return new InitializeCopyOverrideCommand(this, initializeCopyCommand);
    } 

    return null;
  }

  protected Command createCopyToClipboardOverrideCommand(CopyToClipboardCommand copyToClipboardCommand)
  {
    if (!(copyToClipboardCommand instanceof CopyToClipboardOverrideCommand))
    {
      Collection<Object> inputObjects = new ArrayList<Object>();
      Collection<Object> nonInputObjects = new ArrayList<Object>();

      for (Object object : copyToClipboardCommand.getSourceObjects())
      {
        if (mappingRoot.isInputObject(object))
        {
          inputObjects.add(object);
        }
        else
        {
          nonInputObjects.add(object);
        }
      }

      if (inputObjects.size() > 0)
      {
        return new CopyToClipboardOverrideCommand(this, nonInputObjects, inputObjects);
      }
    }

    return null;
  }
      
  protected Command createPasteFromClipboardOverrideCommand(PasteFromClipboardCommand pasteFromClipboardCommand)
  {
    if (!isSameEditingDomainAdapterFactory && optimizeCopy && !(pasteFromClipboardCommand instanceof PasteFromClipboardOverrideCommand))
    {
      return new PasteFromClipboardOverrideCommand(this, pasteFromClipboardCommand);
    }
    return null;
  }

  protected Command createDragAndDropOverrideCommand(DragAndDropCommand dragAndDropCommand)
  {
    if (!isSameEditingDomainAdapterFactory && optimizeCopy && !(dragAndDropCommand instanceof DragAndDropOverrideCommand))
    {
      return new DragAndDropOverrideCommand(this, dragAndDropCommand);
    }
    return null;
  }

  protected EObject getCorrespondingType(EObject sourceType, HashMap<String, String> typeTable)
  {
    EObject result = null;
    EClassifier sourceClassifier = (EClassifier)sourceType;
    String sourceTypeName = sourceClassifier.getEPackage().getNsURI() + packageTypeSeparator + sourceClassifier.getName();
    String targetTypeName = typeTable.get(sourceTypeName);
        
    if (targetTypeName != null) 
    {
      int pos = targetTypeName.indexOf(packageTypeSeparator);
      if (pos != -1) 
      {
        String pkgName = targetTypeName.substring(0, pos);
        EPackage targetPkg = EPackage.Registry.INSTANCE.getEPackage(pkgName);
        targetTypeName = targetTypeName.substring(pos + 1, targetTypeName.length());
        result = targetPkg.getEClassifier(targetTypeName);
      } 
      else
      {
        throw 
          new RuntimeException
            (MappingPlugin.INSTANCE.getString
              ("_EXC_AdapterFactoryMappingDomain_getCorrespondingType_failed", 
               new Object [] { targetTypeName, String.valueOf(packageTypeSeparator) }));
      }
    }
        
    return result;
  } 

  protected List<String> parseName(String sourceName, char sourceSeparator)
  {
    List<String> result = new ArrayList<String>();

    StringBuffer currentWord = new StringBuffer();
    int length = sourceName.length();
    boolean lastIsLower = false;

    for (int index=0; index<length; index++)
    {
      char curChar = sourceName.charAt(index);
      if (Character.isUpperCase(curChar) || curChar == sourceSeparator)
      {
        if (lastIsLower || curChar == sourceSeparator)
        {
          result.add(currentWord.toString());
          currentWord = new StringBuffer();
        }
        lastIsLower = false;
      }
      else
      {
        if (!lastIsLower)
        {
          int currentWordLength = currentWord.length();
          if (currentWordLength > 1)
          {
            char lastChar = currentWord.charAt(--currentWordLength);
            currentWord.setLength(currentWordLength);
            result.add(currentWord.toString());
            currentWord = new StringBuffer();
            currentWord.append(lastChar);
          }
        }
        lastIsLower = true;
      }
      if (curChar != sourceSeparator)
      {
        currentWord.append(curChar);
      }
    }

    result.add(currentWord.toString());
    return result;
  }

  protected String convertName
    (String sourceName, 
     char sourceSeparator, 
     int sourceCase, 
     List<String> sourceShortForms, 
     List<String> sourceLongForms,
     char targetSeparator, 
     int targetCase, 
     List<String> targetShortForms, 
     List<String> targetLongForms)
  {
    String result = convertNameForm(sourceName, sourceShortForms, sourceLongForms);

    if (targetSeparator == LABEL_NO_SEPARATOR) 
    {
      result = convertCase(result, sourceCase, targetCase, sourceSeparator);
      result = convertSeparator(result, sourceSeparator, targetSeparator, sourceCase);
    }
    else 
    {
      result = convertSeparator(result, sourceSeparator, targetSeparator, sourceCase);
      result = convertCase(result, sourceCase, targetCase, sourceSeparator);
    }

    result = convertNameForm(result, targetLongForms, targetShortForms);

    return result;
  }
   
  protected String convertCase(String sourceName, int sourceCase, int targetCase, char sourceSeparator) 
  {
    if (targetCase == sourceCase)
    {
      return sourceName;
    }

    switch (targetCase) 
    {
      case LABEL_UPPER:
      {
        return sourceName.toUpperCase();
      }
      case LABEL_LOWER:
      {
        return sourceName.toLowerCase();
      }
      case LABEL_MIXED:
      {
        StringBuffer newString = new StringBuffer();
        int lastIndex = 0;
        int newIndex = 0;
        for(;;) 
        {
          newIndex = sourceName.indexOf(sourceSeparator, lastIndex);
          if (newIndex != -1 && ++newIndex < sourceName.length()) 
          {
            newString.append(sourceName.substring(lastIndex, newIndex).toLowerCase());
            newString.append(Character.toUpperCase(sourceName.charAt(newIndex)));
            lastIndex = newIndex + 1;
          }
          else 
          {
            newString.append(sourceName.substring(lastIndex).toLowerCase());
            break;
          }
        }
        return newString.toString();
      }
      default:
      {
        return null;
      }
    }
  }    

  protected String convertSeparator(String sourceName, char sourceSeparator, char targetSeparator, int sourceCase) 
  {
    if (targetSeparator == sourceSeparator)
    {
      return sourceName;
    }

    if (sourceSeparator != LABEL_NO_SEPARATOR && targetSeparator != LABEL_NO_SEPARATOR)
    {
      return sourceName.replace(sourceSeparator, targetSeparator);
    }

    if (targetSeparator == LABEL_NO_SEPARATOR) 
    {
      // remove all sourceSeparator characters
      StringBuffer newString = new StringBuffer();
      int lastIndex = 0;
      int newIndex = 0;
      for(;;) 
      {
        newIndex = sourceName.indexOf(sourceSeparator, lastIndex);
        if (newIndex != -1) 
        {
          newString.append(sourceName.substring(lastIndex, newIndex));
          lastIndex = newIndex + 1;
        }
        else 
        {
          newString.append(sourceName.substring(lastIndex));
          break;
        }
      }
      return newString.toString();
    }
    else 
    { 
      // sourceSeparator == LABEL_NO_SEPARATOR
      if (sourceCase == LABEL_MIXED) 
      {
        // add a targetSeparator in front of every upper case character
        StringBuffer newString = new StringBuffer();
        int length = sourceName.length();
        for (int index=0; index<length; index++) 
        {
          char curChar = sourceName.charAt(index);
          if (Character.isUpperCase(curChar) && index != 0)
            newString.append(targetSeparator);
          newString.append(curChar);
        }
        return newString.toString();
      }
    }

    return sourceName;
  } 

  protected String convertNameForm(String name, List<String> fromStrings, List<String> toStrings) 
  {
    String newName = name;

    for (int i=0; i<fromStrings.size(); i++) 
    {
      String fromString = fromStrings.get(i);
      if (name.indexOf(fromString) != -1) 
      {
        String toString = toStrings.get(i);
        newName = change(newName, fromString, toString);
      }
    }

    return newName;
  }    

  //FB The following method is copied from com.ibm.etools.b2b.util.StringUtility
  //FB because the mapping framework cannot have a dependency on b2b. What we really
  //FB need to do is get methods like this moved to vabase so that everyone can
  //FB use them.
  // change all occurrences of oldPat to newPat
  protected String change(String in, String oldPat, String newPat)
  {
    if (oldPat.length() == 0)
      return in;
    if (oldPat.length() == 1 && newPat.length() == 1)
      return in.replace(oldPat.charAt(0), newPat.charAt(0));

    int lastIndex = 0;
    int newIndex = 0;
    StringBuffer newString = new StringBuffer();
    for(;;)
    {
      newIndex = in.indexOf(oldPat, lastIndex);
      if (newIndex != -1)
      {
        newString.append(in.substring(lastIndex, newIndex) + newPat);
        lastIndex = newIndex + oldPat.length();
      }
      else
      {
        newString.append(in.substring(lastIndex));
        break;
      }
    }
    return newString.toString();
  }    
}
