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.ui.actions;
  
  import java.lang.reflect.InvocationTargetException;
  import java.util.Comparator;
  
  import org.eclipse.core.runtime.CoreException;
  import org.eclipse.jface.action.IAction;
  import org.eclipse.jface.action.IStatusLineManager;
  import org.eclipse.jface.dialogs.MessageDialog;
  import org.eclipse.jface.operation.IRunnableContext;
  import org.eclipse.jface.text.DocumentEvent;
  import org.eclipse.jface.text.IEditingSupport;
  import org.eclipse.jface.text.IEditingSupportRegistry;
  import org.eclipse.jface.text.IRegion;
  import org.eclipse.jface.text.IRewriteTarget;
  import org.eclipse.jface.text.ITextSelection;
  import org.eclipse.jface.text.source.ISourceViewer;
  import org.eclipse.jface.viewers.ILabelProvider;
  import org.eclipse.jface.viewers.ISelection;
  import org.eclipse.jface.viewers.IStructuredSelection;
  import org.eclipse.jface.viewers.StructuredSelection;
  import org.eclipse.jface.window.Window;
  import org.eclipse.ui.IEditorPart;
  import org.eclipse.ui.IObjectActionDelegate;
  import org.eclipse.ui.IWorkbenchPart;
  import org.eclipse.ui.IWorkbenchSite;
  import org.eclipse.ui.PlatformUI;
  import org.eclipse.ui.progress.IProgressService;
  import org.eclipse.wst.jsdt.core.IJavaScriptUnit;
  import org.eclipse.wst.jsdt.core.IJavaScriptElement;
  import org.eclipse.wst.jsdt.core.ISourceRange;
  import org.eclipse.wst.jsdt.core.compiler.IProblem;
  import org.eclipse.wst.jsdt.core.dom.JavaScriptUnit;
  import org.eclipse.wst.jsdt.core.search.TypeNameMatch;
  import org.eclipse.wst.jsdt.internal.corext.codemanipulation.CodeGenerationSettings;
  import org.eclipse.wst.jsdt.internal.corext.codemanipulation.OrganizeImportsOperation;
  import org.eclipse.wst.jsdt.internal.corext.codemanipulation.OrganizeImportsOperation.IChooseImportQuery;
  import org.eclipse.wst.jsdt.internal.corext.util.History;
  import org.eclipse.wst.jsdt.internal.corext.util.Messages;
  import org.eclipse.wst.jsdt.internal.corext.util.QualifiedTypeNameHistory;
  import org.eclipse.wst.jsdt.internal.ui.IJavaHelpContextIds;
  import org.eclipse.wst.jsdt.internal.ui.JavaScriptPlugin;
  import org.eclipse.wst.jsdt.internal.ui.actions.ActionMessages;
  import org.eclipse.wst.jsdt.internal.ui.actions.ActionUtil;
  import org.eclipse.wst.jsdt.internal.ui.actions.MultiOrganizeImportAction;
  import org.eclipse.wst.jsdt.internal.ui.actions.WorkbenchRunnableAdapter;
  import org.eclipse.wst.jsdt.internal.ui.dialogs.MultiElementListSelectionDialog;
  import org.eclipse.wst.jsdt.internal.ui.javaeditor.ASTProvider;
  import org.eclipse.wst.jsdt.internal.ui.javaeditor.EditorUtility;
  import org.eclipse.wst.jsdt.internal.ui.javaeditor.JavaEditor;
  import org.eclipse.wst.jsdt.internal.ui.preferences.JavaPreferencesSettings;
  import org.eclipse.wst.jsdt.internal.ui.util.ElementValidator;
  import org.eclipse.wst.jsdt.internal.ui.util.ExceptionHandler;
  import org.eclipse.wst.jsdt.internal.ui.util.TypeNameMatchLabelProvider;
  import org.eclipse.wst.jsdt.ui.JavaScriptUI;
  
  import com.ibm.icu.text.Collator;
  
  /**
   * Organizes the imports of a compilation unit.
   * <p>
   * The action is applicable to selections containing elements of
   * type <code>IJavaScriptUnit</code> or <code>IPackage
   * </code>.
   *
   * <p>
   * This class may be instantiated; it is not intended to be subclassed.
   * </p>
   * 
   *
   * Provisional API: This class/interface is part of an interim API that is still under development and expected to
   * change significantly before reaching stability. It is being made available at this early stage to solicit feedback
   * from pioneering adopters on the understanding that any code that uses this API will almost certainly be broken
   * (repeatedly) as the API evolves.
   */
  public class OrganizeImportsAction extends SelectionDispatchAction {
  
  	private static final OrganizeImportComparator ORGANIZE_IMPORT_COMPARATOR= new OrganizeImportComparator();
  	
  	private JavaEditor fEditor;
  	/** <code>true</code> if the query dialog is showing. */
  	private boolean fIsQueryShowing= false;
  	private final MultiOrganizeImportAction fCleanUpDelegate;
  
  	/* (non-Javadoc)
  	 * Class implements IObjectActionDelegate
  	 */
  	public static class ObjectDelegate implements IObjectActionDelegate {
  		private OrganizeImportsAction fAction;
  		public void setActivePart(IAction action, IWorkbenchPart targetPart) {
  			fAction= new OrganizeImportsAction(targetPart.getSite());
  		}
  		public void run(IAction action) {
  			fAction.run();
  		}
  		public void selectionChanged(IAction action, ISelection selection) {
  			if (fAction == null)
  				action.setEnabled(false);
  		}
  	}
  	
  	private static final class OrganizeImportComparator implements Comparator {
  		
  		public int compare(Object o1, Object o2) {
  			if (((String)o1).equals(o2))
  				return 0;
  			
  			History history= QualifiedTypeNameHistory.getDefault();
  			
  			int pos1= history.getPosition(o1);
  			int pos2= history.getPosition(o2);
  			
  			if (pos1 == pos2)
  				return Collator.getInstance().compare(o1, o2);
  			
  			if (pos1 > pos2) {
  				return -1;
  			} else {
  				return 1;
  			}
  		}
  		
  	}
  
  	/**
  	 * Creates a new <code>OrganizeImportsAction</code>. The action requires
  	 * that the selection provided by the site's selection provider is of type <code>
  	 * org.eclipse.jface.viewers.IStructuredSelection</code>.
  	 * 
  	 * @param site the site providing context information for this action
  	 */
  	public OrganizeImportsAction(IWorkbenchSite site) {
  		super(site);
  		
  		fCleanUpDelegate= new MultiOrganizeImportAction(site);
  		
  		setText(ActionMessages.OrganizeImportsAction_label); 
  		setToolTipText(ActionMessages.OrganizeImportsAction_tooltip); 
  		setDescription(ActionMessages.OrganizeImportsAction_description);
  
  		PlatformUI.getWorkbench().getHelpSystem().setHelp(this, IJavaHelpContextIds.ORGANIZE_IMPORTS_ACTION);					
  	}
  	
  	/**
  	 * Note: This constructor is for internal use only. Clients should not call this constructor.
  	 * @param editor the JavaScript editor
  	 */
  	public OrganizeImportsAction(JavaEditor editor) {
  		super(editor.getEditorSite());
  		
  		fEditor= editor;
  		fCleanUpDelegate= new MultiOrganizeImportAction(editor);
  		
  		setText(ActionMessages.OrganizeImportsAction_label); 
  		setToolTipText(ActionMessages.OrganizeImportsAction_tooltip); 
  		setDescription(ActionMessages.OrganizeImportsAction_description);
  
  		PlatformUI.getWorkbench().getHelpSystem().setHelp(this, IJavaHelpContextIds.ORGANIZE_IMPORTS_ACTION);
  
  		setEnabled(fCleanUpDelegate.isEnabled());
  	}
  	
  	/* (non-Javadoc)
  	 * Method declared on SelectionDispatchAction.
  	 */
  	public void selectionChanged(ITextSelection selection) {
  		fCleanUpDelegate.selectionChanged(selection);
  		setEnabled(fCleanUpDelegate.isEnabled());
  	}
  	
  	/* (non-Javadoc)
  	 * Method declared on SelectionDispatchAction.
  	 */
  	public void selectionChanged(IStructuredSelection selection) {
  		fCleanUpDelegate.selectionChanged(selection);
  		setEnabled(fCleanUpDelegate.isEnabled());
  	}
  	
  	/* (non-Javadoc)
  	 * Method declared on SelectionDispatchAction.
  	 */
  	public void run(ITextSelection selection) {
  		IJavaScriptUnit cu= getCompilationUnit(fEditor);
  		if (cu != null) {
  			run(cu);
  		}
  	}
  
  	private static IJavaScriptUnit getCompilationUnit(JavaEditor editor) {
  		IJavaScriptElement element= JavaScriptUI.getEditorInputJavaElement(editor.getEditorInput());
  		if (!(element instanceof IJavaScriptUnit))
  			return null;
  		
  		return (IJavaScriptUnit)element;
  	}
  	
  	/* (non-Javadoc)
  	 * Method declared on SelectionDispatchAction.
  	 */
  	public void run(IStructuredSelection selection) {
  		IJavaScriptUnit[] cus= fCleanUpDelegate.getCompilationUnits(selection);
  		if (cus.length == 0) {
  			MessageDialog.openInformation(getShell(), ActionMessages.OrganizeImportsAction_EmptySelection_title, ActionMessages.OrganizeImportsAction_EmptySelection_description);
  		} else if (cus.length == 1) {
  			run(cus[0]);
  		} else {
  			fCleanUpDelegate.run(selection);
  		}
  	}
  	
  	/**
  	 * Perform organize import on multiple compilation units. No editors are opened.
  	 * @param cus The compilation units to run on
  	 */
  	public void runOnMultiple(final IJavaScriptUnit[] cus) {
  		if (cus.length == 0)
  			return;
  		
  		fCleanUpDelegate.run(new StructuredSelection(cus));	
  	}
  
  	/**
  	 * Note: This method is for internal use only. Clients should not call this method.
  	 * @param cu The compilation unit to process
  	 */
  	public void run(IJavaScriptUnit cu) {
  		if (!ElementValidator.check(cu, getShell(), ActionMessages.OrganizeImportsAction_error_title, fEditor != null)) 
  			return;
  		if (!ActionUtil.isEditable(fEditor, getShell(), cu))
  			return;
  		
  		IEditingSupport helper= createViewerHelper();
  		try {
  			CodeGenerationSettings settings= JavaPreferencesSettings.getCodeGenerationSettings(cu.getJavaScriptProject());
  			
  			if (fEditor == null && EditorUtility.isOpenInEditor(cu) == null) {
  				IEditorPart editor= JavaScriptUI.openInEditor(cu);
  				if (editor instanceof JavaEditor) {
  					fEditor= (JavaEditor) editor;
  				}			
  			}
  			
  			JavaScriptUnit astRoot= JavaScriptPlugin.getDefault().getASTProvider().getAST(cu, ASTProvider.WAIT_ACTIVE_ONLY, null);
  			
  			OrganizeImportsOperation op= new OrganizeImportsOperation(cu, astRoot, settings.importIgnoreLowercase, !cu.isWorkingCopy(), true, createChooseImportQuery());
  		
  			IRewriteTarget target= null;
  			if (fEditor != null) {
  				target= (IRewriteTarget) fEditor.getAdapter(IRewriteTarget.class);
  				if (target != null) {
  					target.beginCompoundChange();
  				}
  			}
  			
  			IProgressService progressService= PlatformUI.getWorkbench().getProgressService();
  			IRunnableContext context= getSite().getWorkbenchWindow();
  			if (context == null) {
  				context= progressService;
  			}
  			try {
  				registerHelper(helper);
  				progressService.runInUI(context, new WorkbenchRunnableAdapter(op, op.getScheduleRule()), op.getScheduleRule());
  				IProblem parseError= op.getParseError();
  				if (parseError != null) {
  					String message= Messages.format(ActionMessages.OrganizeImportsAction_single_error_parse, parseError.getMessage()); 
  					MessageDialog.openInformation(getShell(), ActionMessages.OrganizeImportsAction_error_title, message); 
  					if (fEditor != null && parseError.getSourceStart() != -1) {
  						fEditor.selectAndReveal(parseError.getSourceStart(), parseError.getSourceEnd() - parseError.getSourceStart() + 1);
  					}
  				} else {
  					if (fEditor != null) {
  						setStatusBarMessage(getOrganizeInfo(op));
  					}
  				}
  			} catch (InvocationTargetException e) {
  				ExceptionHandler.handle(e, getShell(), ActionMessages.OrganizeImportsAction_error_title, ActionMessages.OrganizeImportsAction_error_message); 
  			} catch (InterruptedException e) {
  			} finally {
  				deregisterHelper(helper);
  				if (target != null) {
  					target.endCompoundChange();
  				}
  			}
  		} catch (CoreException e) {	
  			ExceptionHandler.handle(e, getShell(), ActionMessages.OrganizeImportsAction_error_title, ActionMessages.OrganizeImportsAction_error_message); 
  		}
  	}
  	
  	private String getOrganizeInfo(OrganizeImportsOperation op) {
  		int nImportsAdded= op.getNumberOfImportsAdded();
  		if (nImportsAdded >= 0) {
  			return Messages.format(ActionMessages.OrganizeImportsAction_summary_added, String.valueOf(nImportsAdded)); 
  		} else {
  			return Messages.format(ActionMessages.OrganizeImportsAction_summary_removed, String.valueOf(-nImportsAdded)); 
  		}
  	}
  		
  	private IChooseImportQuery createChooseImportQuery() {
  		return new IChooseImportQuery() {
  			public TypeNameMatch[] chooseImports(TypeNameMatch[][] openChoices, ISourceRange[] ranges) {
  				return doChooseImports(openChoices, ranges);
  			}
  		};
  	}
  	
  	private TypeNameMatch[] doChooseImports(TypeNameMatch[][] openChoices, final ISourceRange[] ranges) {
  		// remember selection
  		ISelection sel= fEditor != null ? fEditor.getSelectionProvider().getSelection() : null;
  		TypeNameMatch[] result= null;
  		ILabelProvider labelProvider= new TypeNameMatchLabelProvider(TypeNameMatchLabelProvider.SHOW_FULLYQUALIFIED);
  		
  		MultiElementListSelectionDialog dialog= new MultiElementListSelectionDialog(getShell(), labelProvider) {
  			protected void handleSelectionChanged() {
  				super.handleSelectionChanged();
  				// show choices in editor
  				doListSelectionChanged(getCurrentPage(), ranges);
  			}
  		};
  		fIsQueryShowing= true;
  		dialog.setTitle(ActionMessages.OrganizeImportsAction_selectiondialog_title); 
  		dialog.setMessage(ActionMessages.OrganizeImportsAction_selectiondialog_message);
  		dialog.setElements(openChoices);
  		dialog.setComparator(ORGANIZE_IMPORT_COMPARATOR);
  		if (dialog.open() == Window.OK) {
  			Object[] res= dialog.getResult();			
  			result= new TypeNameMatch[res.length];
  			for (int i= 0; i < res.length; i++) {
  				Object[] array= (Object[]) res[i];
  				if (array.length > 0) {
  					result[i]= (TypeNameMatch) array[0];
  					QualifiedTypeNameHistory.remember(result[i].getFullyQualifiedName());
  				}
  			}
  		}
  		// restore selection
  		if (sel instanceof ITextSelection) {
  			ITextSelection textSelection= (ITextSelection) sel;
  			fEditor.selectAndReveal(textSelection.getOffset(), textSelection.getLength());
  		}
  		fIsQueryShowing= false;
  		return result;
  	}
  	
  	private void doListSelectionChanged(int page, ISourceRange[] ranges) {
  		if (fEditor != null && ranges != null && page >= 0 && page < ranges.length) {
  			ISourceRange range= ranges[page];
  			fEditor.selectAndReveal(range.getOffset(), range.getLength());
  		}
  	}
  	
  	private void setStatusBarMessage(String message) {
  		IStatusLineManager manager= fEditor.getEditorSite().getActionBars().getStatusLineManager();
  		manager.setMessage(message);
  	}
  	
  	private IEditingSupport createViewerHelper() {
  		return new IEditingSupport() {
  			public boolean isOriginator(DocumentEvent event, IRegion subjectRegion) {
  				return true; // assume true, since we only register while we are active
  			}
  			public boolean ownsFocusShell() {
  				return fIsQueryShowing;
  			}
  			
  		};
  	}
  	
  	private void registerHelper(IEditingSupport helper) {
  		if (fEditor == null)
  			return;
  		ISourceViewer viewer= fEditor.getViewer();
  		if (viewer instanceof IEditingSupportRegistry) {
  			IEditingSupportRegistry registry= (IEditingSupportRegistry) viewer;
  			registry.register(helper);
  		}
  	}
  
  	private void deregisterHelper(IEditingSupport helper) {
  		if (fEditor == null)
  			return;
  		ISourceViewer viewer= fEditor.getViewer();
  		if (viewer instanceof IEditingSupportRegistry) {
  			IEditingSupportRegistry registry= (IEditingSupportRegistry) viewer;
  			registry.unregister(helper);
  		}
  	}
  }
  