content: /*******************************************************************************
   * Copyright (c) 2000, 2008 IBM Corporation and others.
   * All rights reserved. This program and the accompanying materials
   * are made available under the terms of the Eclipse Public License v1.0
   * which accompanies this distribution, and is available at
   * http://www.eclipse.org/legal/epl-v10.html
   *
   * Contributors:
   *     IBM Corporation - initial API and implementation
   *******************************************************************************/
  package org.eclipse.wst.jsdt.internal.corext.refactoring.rename;
  
  import java.util.Collections;
  import java.util.LinkedList;
  import java.util.Map;
  
  import org.eclipse.core.runtime.Assert;
  import org.eclipse.wst.jsdt.core.IField;
  import org.eclipse.wst.jsdt.core.IInitializer;
  import org.eclipse.wst.jsdt.core.IJavaScriptElement;
  import org.eclipse.wst.jsdt.core.IMember;
  import org.eclipse.wst.jsdt.core.IFunction;
  import org.eclipse.wst.jsdt.core.IType;
  import org.eclipse.wst.jsdt.core.Signature;
  
  
  /**
   * Helper class to transplant a IJavaScriptElement handle from a certain state of the
   * Java Model into another.
   * 
   * The changes to the workspace include one type rename, a number of field
   * renames, and a number of method renames including signature changes.
   * 
   * The returned handle exists in the target model state.
   * 
   * 
   * 
   */
  public class RefactoringHandleTransplanter {
  
  	private final IType fOldType;
  	private final IType fNewType;
  	private final Map/*<IJavaScriptElement, String>*/ fRefactoredSimilarElements;
  
  	/**
  	 * @param oldType old type
  	 * @param newType renamed type
  	 * @param refactoredSimilarElements map from similar element (IJavaScriptElement) to new name (String), or <code>null</code>
  	 */
  	public RefactoringHandleTransplanter(IType oldType, IType newType, Map/*<IJavaScriptElement, String>*/ refactoredSimilarElements) {
  		fOldType= oldType;
  		fNewType= newType;
  		if (refactoredSimilarElements == null)
  			fRefactoredSimilarElements= Collections.EMPTY_MAP;
  		else
  			fRefactoredSimilarElements= refactoredSimilarElements;
  	}
  
  	/**
  	 * Converts the handle. Handle need not exist, but must be a source
  	 * reference.
  	 * 
  	 * @param handle
  	 * @return the new handle
  	 */
  	public IMember transplantHandle(IMember handle) {
  
  		/*
  		 * Create a list of handles from top-level type to the handle
  		 */
  		final LinkedList oldElements= new LinkedList();
  		addElements(handle, oldElements);
  
  		/*
  		 * Step through the elements and re-locate them in the new parents.
  		 */
  		final IMember[] newElements= convertElements((IMember[]) oldElements.toArray(new IMember[0]));
  
  		return newElements[newElements.length - 1];
  	}
  
  	private void addElements(IMember element, LinkedList chain) {
  		chain.addFirst(element);
  		IJavaScriptElement parent= element.getParent();
  		if (parent instanceof IMember)
  			addElements((IMember) parent, chain);
  	}
  	
  	private IMember[] convertElements(IMember[] oldElements) {
  
  		final IMember[] newElements= new IMember[oldElements.length];
  		final IMember first= oldElements[0];
  
  		Assert.isTrue(first instanceof IType);
  
  		if (first.equals(fOldType))
  			// We renamed a top level type.
  			newElements[0]= fNewType;
  		else
  			newElements[0]= first;
  
  		/*
  		 * Note that we only need to translate the information necessary to
  		 * create new handles. For example, the return type of a method is not
  		 * relevant; neither is information about generic specifics in types.
  		 */
  
  		for (int i= 1; i < oldElements.length; i++) {
  			final IJavaScriptElement newParent= newElements[i - 1];
  			final IJavaScriptElement currentElement= oldElements[i];
  			switch (newParent.getElementType()) {
  				case IJavaScriptElement.TYPE: {
  					switch (currentElement.getElementType()) {
  						case IJavaScriptElement.TYPE: {
  							final String newName= resolveTypeName((IType) currentElement);
  							newElements[i]= ((IType) newParent).getType(newName);
  							break;
  						}
  						case IJavaScriptElement.METHOD: {
  							final String newName= resolveElementName(currentElement);
  							final String[] newParameterTypes= resolveParameterTypes((IFunction) currentElement);
  							newElements[i]= ((IType) newParent).getFunction(newName, newParameterTypes);
  							break;
  						}
  						case IJavaScriptElement.INITIALIZER: {
  							final IInitializer initializer= (IInitializer) currentElement;
  							newElements[i]= ((IType) newParent).getInitializer(initializer.getOccurrenceCount());
  							break;
  						}
  						case IJavaScriptElement.FIELD: {
  							final String newName= resolveElementName(currentElement);
  							newElements[i]= ((IType) newParent).getField(newName);
  							break;
  						}
  					}
  					break;
  				}
  				case IJavaScriptElement.METHOD: {
  					switch (currentElement.getElementType()) {
  						case IJavaScriptElement.TYPE: {
  							newElements[i]= resolveTypeInMember((IFunction) newParent, (IType) currentElement);
  							break;
  						}
  					}
  					break;
  				}
  				case IJavaScriptElement.INITIALIZER: {
  					switch (currentElement.getElementType()) {
  						case IJavaScriptElement.TYPE: {
  							newElements[i]= resolveTypeInMember((IInitializer) newParent, (IType) currentElement);
  							break;
  						}
  					}
  					break;
  				}
  				case IJavaScriptElement.FIELD: {
  					switch (currentElement.getElementType()) {
  						case IJavaScriptElement.TYPE: {
  							// anonymous type in field declaration
  							newElements[i]= resolveTypeInMember((IField) newParent, (IType) currentElement);
  							break;
  						}
  					}
  					break;
  				}
  			}
  		}
  		return newElements;
  	}
  
  	private String[] resolveParameterTypes(IFunction method) {
  		final String[] oldParameterTypes= method.getParameterTypes();
  		final String[] newparams= new String[oldParameterTypes.length];
  
  		final String[] possibleOldSigs= new String[4];
  		possibleOldSigs[0]= Signature.createTypeSignature(fOldType.getElementName(), false);
  		possibleOldSigs[1]= Signature.createTypeSignature(fOldType.getElementName(), true);
  		possibleOldSigs[2]= Signature.createTypeSignature(fOldType.getFullyQualifiedName(), false);
  		possibleOldSigs[3]= Signature.createTypeSignature(fOldType.getFullyQualifiedName(), true);
  
  		final String[] possibleNewSigs= new String[4];
  		possibleNewSigs[0]= Signature.createTypeSignature(fNewType.getElementName(), false);
  		possibleNewSigs[1]= Signature.createTypeSignature(fNewType.getElementName(), true);
  		possibleNewSigs[2]= Signature.createTypeSignature(fNewType.getFullyQualifiedName(), false);
  		possibleNewSigs[3]= Signature.createTypeSignature(fNewType.getFullyQualifiedName(), true);
  
  		// Textually replace all occurrences
  		// This handles stuff like Map<SomeClass, some.package.SomeClass>
  		for (int i= 0; i < oldParameterTypes.length; i++) {
  			newparams[i]= oldParameterTypes[i];
  			for (int j= 0; j < possibleOldSigs.length; j++) {
  				newparams[i]= replaceAll(newparams[i], possibleOldSigs[j], possibleNewSigs[j]);
  			}
  		}
  		return newparams;
  	}
  
  	private String resolveElementName(IJavaScriptElement element) {
  		final String newName= (String) fRefactoredSimilarElements.get(element);
  		if (newName != null)
  			return newName;
  		else
  			return element.getElementName();
  	}
  
  	private IMember resolveTypeInMember(final IMember newParent, IType oldChild) {
  		// Local type or anonymous type. Only local types can be renamed.
  		String newName= ""; //$NON-NLS-1$
  		if (oldChild.getElementName().length() != 0)
  			newName= resolveTypeName(oldChild);
  		return newParent.getType(newName, oldChild.getOccurrenceCount());
  	}
  
  	private String resolveTypeName(IType type) {
  		return type.equals(fOldType) ? fNewType.getElementName() : type.getElementName();
  	}
  
      private static String replaceAll(final String source, final String replaceFrom, final String replaceTo) {
  		final StringBuffer buf= new StringBuffer(source.length());
  		int currentIndex= 0;
  		int matchIndex;
  		while ((matchIndex= source.indexOf(replaceFrom, currentIndex)) != -1) {
  			buf.append(source.substring(currentIndex, matchIndex));
  			buf.append(replaceTo);
  			currentIndex= matchIndex + replaceFrom.length();
  		}
  		buf.append(source.substring(currentIndex));
  		return buf.toString();
  	}
  }
  