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.ArrayList;
  import java.util.Arrays;
  import java.util.Iterator;
  import java.util.List;
  
  import org.eclipse.core.runtime.CoreException;
  import org.eclipse.core.runtime.IProgressMonitor;
  import org.eclipse.core.runtime.IStatus;
  import org.eclipse.core.runtime.OperationCanceledException;
  import org.eclipse.core.runtime.Status;
  import org.eclipse.core.runtime.SubProgressMonitor;
  import org.eclipse.jface.dialogs.IDialogConstants;
  import org.eclipse.jface.dialogs.MessageDialog;
  import org.eclipse.jface.operation.IRunnableWithProgress;
  import org.eclipse.jface.text.ITextSelection;
  import org.eclipse.jface.viewers.ArrayContentProvider;
  import org.eclipse.jface.viewers.ISelection;
  import org.eclipse.jface.viewers.ISelectionChangedListener;
  import org.eclipse.jface.viewers.IStructuredSelection;
  import org.eclipse.jface.viewers.LabelProvider;
  import org.eclipse.jface.viewers.SelectionChangedEvent;
  import org.eclipse.swt.SWT;
  import org.eclipse.swt.custom.BusyIndicator;
  import org.eclipse.swt.events.SelectionAdapter;
  import org.eclipse.swt.events.SelectionEvent;
  import org.eclipse.swt.graphics.Image;
  import org.eclipse.swt.graphics.Point;
  import org.eclipse.swt.widgets.Button;
  import org.eclipse.swt.widgets.Composite;
  import org.eclipse.swt.widgets.Control;
  import org.eclipse.swt.widgets.Shell;
  import org.eclipse.ui.IWorkbenchSite;
  import org.eclipse.ui.PlatformUI;
  import org.eclipse.wst.jsdt.core.IJavaScriptUnit;
  import org.eclipse.wst.jsdt.core.IJavaScriptElement;
  import org.eclipse.wst.jsdt.core.IJavaScriptProject;
  import org.eclipse.wst.jsdt.core.IPackageFragment;
  import org.eclipse.wst.jsdt.core.IPackageFragmentRoot;
  import org.eclipse.wst.jsdt.core.IType;
  import org.eclipse.wst.jsdt.core.JavaScriptModelException;
  import org.eclipse.wst.jsdt.core.compiler.InvalidInputException;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.RefactoringAvailabilityTester;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.nls.NLSElement;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.nls.NLSLine;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.nls.NLSRefactoring;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.nls.NLSScanner;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.reorg.ReorgUtils;
  import org.eclipse.wst.jsdt.internal.corext.util.JavaModelUtil;
  import org.eclipse.wst.jsdt.internal.corext.util.Messages;
  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.SelectionConverter;
  import org.eclipse.wst.jsdt.internal.ui.javaeditor.CompilationUnitEditor;
  import org.eclipse.wst.jsdt.internal.ui.refactoring.RefactoringSaveHelper;
  import org.eclipse.wst.jsdt.internal.ui.refactoring.actions.ListDialog;
  import org.eclipse.wst.jsdt.internal.ui.refactoring.actions.RefactoringStarter;
  import org.eclipse.wst.jsdt.internal.ui.refactoring.nls.ExternalizeWizard;
  import org.eclipse.wst.jsdt.internal.ui.util.ExceptionHandler;
  import org.eclipse.wst.jsdt.ui.JavaScriptElementLabelProvider;
  
  /**
   * Externalizes the strings of a compilation unit or find all strings
   * in a package or project that are not externalized yet. Opens a wizard that
   * gathers additional information to externalize the strings.
   * <p>
   * The action is applicable to structured selections containing elements
   * of type <code>IJavaScriptUnit</code>, <code>IType</code>, <code>IJavaScriptProject</code>,
   * <code>IPackageFragment</code>, and <code>IPackageFragmentRoot</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 ExternalizeStringsAction extends SelectionDispatchAction {
  
  	private CompilationUnitEditor fEditor;
  	
  	private NonNLSElement[] fElements;
  
  	/**
  	 * Creates a new <code>ExternalizeStringsAction</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 ExternalizeStringsAction(IWorkbenchSite site) {
  		super(site);
  		setText(ActionMessages.ExternalizeStringsAction_label); 
  		PlatformUI.getWorkbench().getHelpSystem().setHelp(this, IJavaHelpContextIds.EXTERNALIZE_STRINGS_ACTION);
  	}
  
  	/**
  	 * Note: This constructor is for internal use only. Clients should not call this constructor.
  	 * @param editor the compilation unit editor
  	 */
  	public ExternalizeStringsAction(CompilationUnitEditor editor) {
  		this(editor.getEditorSite());
  		fEditor= editor;
  		setEnabled(fEditor != null && SelectionConverter.canOperateOn(fEditor));
  	}
  	
  	/* (non-Javadoc)
  	 * Method declared on SelectionDispatchAction.
  	 */
  	public void selectionChanged(ITextSelection selection) {
  	}
  	
  	/* (non-Javadoc)
  	 * Method declared on SelectionDispatchAction.
  	 */
  	public void selectionChanged(IStructuredSelection selection) {
  		try {
  			setEnabled(RefactoringAvailabilityTester.isExternalizeStringsAvailable(selection));
  		} catch (JavaScriptModelException e) {
  			if (JavaModelUtil.isExceptionToBeLogged(e))
  				JavaScriptPlugin.log(e);
  			setEnabled(false);//no UI - happens on selection changes
  		}
  	}
  
  	/* (non-Javadoc)
  	 * Method declared on SelectionDispatchAction.
  	 */
  	public void run(ITextSelection selection) {
  		IJavaScriptElement element= SelectionConverter.getInput(fEditor);
  		if (!(element instanceof IJavaScriptUnit))
  			return;
  		run((IJavaScriptUnit)element);
  	}
  	
  	/* (non-Javadoc)
  	 * Method declared on SelectionDispatchAction.
  	 */
  	public void run(IStructuredSelection selection) {
  		IJavaScriptUnit unit= getCompilationUnit(selection);
  		if (unit != null) {//run on cu 
  			run(unit);
  		} else {
  			//run on multiple
  			try {
  				PlatformUI.getWorkbench().getProgressService().run(true, true, createRunnable(selection));
  			} catch(InvocationTargetException e) {
  				ExceptionHandler.handle(e, getShell(), 
  					ActionMessages.ExternalizeStringsAction_dialog_title, 
  					ActionMessages.FindStringsToExternalizeAction_error_message); 
  				return;
  			} catch(InterruptedException e) {
  				// OK
  				return;
  			}
  			showResults();	
  		}
  	}
  	
  	/**
  	 * Note: this method is for internal use only. Clients should not call this method.
  	 */
  	public void run(final IJavaScriptUnit unit) {
  		if (!ActionUtil.isEditable(fEditor, getShell(), unit))
  			return;
  		
  		BusyIndicator.showWhile(getShell().getDisplay(), new Runnable() {
  			public void run() {
  				try {
  					if (unit != null && unit.exists()) {
  						NLSRefactoring refactoring= NLSRefactoring.create(unit);
  						if (refactoring != null)
  							new RefactoringStarter().activate(refactoring, new ExternalizeWizard(refactoring), getShell(), ActionMessages.ExternalizeStringsAction_dialog_title, RefactoringSaveHelper.SAVE_NON_JAVA_UPDATES); 
  					}
  				} catch(JavaScriptModelException e) {
  					ExceptionHandler.handle(e, getShell(), ActionMessages.ExternalizeStringsAction_dialog_title, ActionMessages.ExternalizeStringsAction_dialog_message); 
  				}
  			}
  		});
  	}
  
  	private static IJavaScriptUnit getCompilationUnit(IStructuredSelection selection) {
  		if (selection.isEmpty() || selection.size() != 1)
  			return null;
  		Object first= selection.getFirstElement();
  		if (first instanceof IJavaScriptUnit) 
  			return (IJavaScriptUnit) first;
  		if (first instanceof IType)
  			return ((IType) first).getJavaScriptUnit();
  		return null;
  	}
  	
  	private IRunnableWithProgress createRunnable(final IStructuredSelection selection) {
  		return new IRunnableWithProgress() {
  			public void run(IProgressMonitor pm) throws InvocationTargetException {
  				try {
  					fElements= doRun(selection, pm);
  				} catch (CoreException e) {
  					throw new InvocationTargetException(e);
  				}
  			}
  		};
  	}
  
  	private NonNLSElement[] doRun(IStructuredSelection selection, IProgressMonitor pm) throws CoreException {
  		List elements= getSelectedElementList(selection);
  		if (elements == null || elements.isEmpty())
  			return new NonNLSElement[0];
  
  		pm.beginTask(ActionMessages.FindStringsToExternalizeAction_find_strings, elements.size()); 
  					
  		try{
  			List result= new ArrayList();	
  			for (Iterator iter= elements.iterator(); iter.hasNext();) {
  				Object obj= iter.next();
  				if (obj instanceof IJavaScriptElement) {
  					IJavaScriptElement element= (IJavaScriptElement) obj;
  					int elementType= element.getElementType();
  					
  					if (elementType == IJavaScriptElement.PACKAGE_FRAGMENT) {
  						result.addAll(analyze((IPackageFragment) element, new SubProgressMonitor(pm, 1)));
  					} else if (elementType == IJavaScriptElement.PACKAGE_FRAGMENT_ROOT) {
  						IPackageFragmentRoot root= (IPackageFragmentRoot)element;
  						if (!root.isExternal() && !ReorgUtils.isClassFolder(root)) {
  							result.addAll(analyze((IPackageFragmentRoot) element, new SubProgressMonitor(pm, 1)));
  						} else {
  							pm.worked(1);
  						}
  					} else if (elementType == IJavaScriptElement.JAVASCRIPT_PROJECT) {
  						result.addAll(analyze((IJavaScriptProject) element, new SubProgressMonitor(pm, 1)));
  					} else if (elementType == IJavaScriptElement.JAVASCRIPT_UNIT) {
  						IJavaScriptUnit cu= (IJavaScriptUnit)element;
  						if (cu.exists()) {
  							NonNLSElement nlsElement= analyze(cu);
  							if (nlsElement != null) {
  								result.add(nlsElement);
  							}							
  						}
  						pm.worked(1);
  					} else if (elementType == IJavaScriptElement.TYPE) {
  						IType type= (IType)element;
  						IJavaScriptUnit cu= type.getJavaScriptUnit();
  						if (cu != null && cu.exists()) {
  							NonNLSElement nlsElement= analyze(cu);
  							if (nlsElement != null) {
  								result.add(nlsElement);
  							}							
  						}
  						pm.worked(1);
  					} else {
  						pm.worked(1);
  					}
  				} else {
  					pm.worked(1);
  				}
  			}
  			return (NonNLSElement[]) result.toArray(new NonNLSElement[result.size()]);
  		} finally{
  			pm.done();
  		}
  	}
  
  	private void showResults() {
  		if (noStrings())
  			MessageDialog.openInformation(getShell(), ActionMessages.ExternalizeStringsAction_dialog_title, ActionMessages.FindStringsToExternalizeAction_noStrings); 
  		else
  			new NonNLSListDialog(getShell(), fElements, countStrings()).open();
  	}
  
  	private boolean noStrings() {
  		if (fElements != null) {
  			for (int i= 0; i < fElements.length; i++) {
  				if (fElements[i].count != 0)
  					return false;
  			}
  		}
  		return true;
  	}
  
  	/*
  	 * returns List of Strings
  	 */
  	private List analyze(IPackageFragment pack, IProgressMonitor pm) throws CoreException {
  		try{
  			if (pack == null)
  				return new ArrayList(0);
  				
  			IJavaScriptUnit[] cus= pack.getJavaScriptUnits();
  	
  			pm.beginTask("", cus.length); //$NON-NLS-1$
  			pm.setTaskName(pack.getElementName());
  			
  			List l= new ArrayList(cus.length);
  			for (int i= 0; i < cus.length; i++){
  				pm.subTask(cus[i].getElementName());
  				NonNLSElement element= analyze(cus[i]);
  				if (element != null)
  					l.add(element);
  				pm.worked(1);
  				if (pm.isCanceled())
  					throw new OperationCanceledException();
  			}	
  			return l;					
  		} finally {
  			pm.done();
  		}	
  	}
  
  	/*
  	 * returns List of Strings
  	 */	
  	private List analyze(IPackageFragmentRoot sourceFolder, IProgressMonitor pm) throws CoreException {
  		try{
  			IJavaScriptElement[] children= sourceFolder.getChildren();
  			pm.beginTask("", children.length); //$NON-NLS-1$
  			pm.setTaskName(sourceFolder.getElementName());
  			List result= new ArrayList();
  			for (int i= 0; i < children.length; i++) {
  				IJavaScriptElement iJavaElement= children[i];
  				if (iJavaElement.getElementType() == IJavaScriptElement.PACKAGE_FRAGMENT){
  					IPackageFragment pack= (IPackageFragment)iJavaElement;
  					if (! pack.isReadOnly())
  						result.addAll(analyze(pack, new SubProgressMonitor(pm, 1)));
  					else
  						pm.worked(1);	
  				} else	
  					pm.worked(1);
  			}
  			return result;
  		} finally{
  			pm.done();
  		}	
  	}
  	
  	/*
  	 * returns List of Strings
  	 */
  	private List analyze(IJavaScriptProject project, IProgressMonitor pm) throws CoreException {
  		try{
  			IPackageFragment[] packs= project.getPackageFragments();
  			pm.beginTask("", packs.length); //$NON-NLS-1$
  			List result= new ArrayList();
  			for (int i= 0; i < packs.length; i++) {
  				if (! packs[i].isReadOnly())
  					result.addAll(analyze(packs[i], new SubProgressMonitor(pm, 1)));
  				else 
  					pm.worked(1);	
  			}
  			return result;		
  		} finally{
  			pm.done();
  		}	
  	}
  
  	private int countStrings() {
  		int found= 0;
  		if (fElements != null) {
  			for (int i= 0; i < fElements.length; i++)
  				found+= fElements[i].count;
  		}
  		return found;
  	} 
  
  	private NonNLSElement analyze(IJavaScriptUnit cu) throws CoreException {
  		int count= countNonExternalizedStrings(cu);
  		if (count == 0)
  			return null;
  		else	
  			return new NonNLSElement(cu, count);
  	}
  	
  	private int countNonExternalizedStrings(IJavaScriptUnit cu) throws CoreException {
  		try{
  			NLSLine[] lines= NLSScanner.scan(cu);
  			int result= 0;
  			for (int i= 0; i < lines.length; i++) {
  				result += countNonExternalizedStrings(lines[i]);
  			}
  			return result;
  		} catch (InvalidInputException e) {
  			throw new CoreException(new Status(IStatus.ERROR, JavaScriptPlugin.getPluginId(), IStatus.ERROR,
  				Messages.format(ActionMessages.FindStringsToExternalizeAction_error_cannotBeParsed, cu.getElementName()), 
  				e));
  		}	
  	}
  
  	private int countNonExternalizedStrings(NLSLine line){
  		int result= 0;
  		NLSElement[] elements= line.getElements();
  		for (int i= 0; i < elements.length; i++){
  			if (! elements[i].hasTag())
  				result++;
  		}
  		return result;
  	}
  
  	/**
  	 * returns <code>List</code> of <code>IPackageFragments</code>,  <code>IPackageFragmentRoots</code> or 
  	 * <code>IJavaProjects</code> (all entries are of the same kind)
  	 */
  	private static List getSelectedElementList(IStructuredSelection selection) {
  		if (selection == null)
  			return null;
  			
  		return selection.toList();
  	}
  			
  	//-------private classes --------------
  		
  	private static class NonNLSListDialog extends ListDialog {
  		
  		private static final int OPEN_BUTTON_ID= IDialogConstants.CLIENT_ID + 1;
  		
  		private Button fOpenButton;
  		
  		NonNLSListDialog(Shell parent, NonNLSElement[] input, int count) {
  			super(parent);
  			setInput(Arrays.asList(input));
  			setTitle(ActionMessages.ExternalizeStringsAction_dialog_title);  
  			setMessage(Messages.format(ActionMessages.FindStringsToExternalizeAction_non_externalized, new Object[] {Integer.valueOf(count)} )); 
  			setContentProvider(new ArrayContentProvider());
  			setLabelProvider(createLabelProvider());
  		}
  
  		public void create() {
  			setShellStyle(SWT.DIALOG_TRIM | SWT.RESIZE | SWT.MIN);
  			super.create();
  		}
  
  		protected Point getInitialSize() {
  			return getShell().computeSize(SWT.DEFAULT, SWT.DEFAULT, true);
  		}
  
  		protected Control createDialogArea(Composite parent) {
  			Composite result= (Composite)super.createDialogArea(parent);
  			getTableViewer().addSelectionChangedListener(new ISelectionChangedListener(){
  				public void selectionChanged(SelectionChangedEvent event){
  					if (fOpenButton != null){
  						fOpenButton.setEnabled(! getTableViewer().getSelection().isEmpty());
  					}
  				}
  			});
  			getTableViewer().getTable().addSelectionListener(new SelectionAdapter(){
  				public void widgetDefaultSelected(SelectionEvent e) {
  					NonNLSElement element= (NonNLSElement)e.item.getData();
  					openWizard(element.cu);
  				}
  			});
  			getTableViewer().getTable().setFocus();
  			applyDialogFont(result);		
  			return result;
  		}
  		
  		protected void createButtonsForButtonBar(Composite parent) {
  			fOpenButton= createButton(parent, OPEN_BUTTON_ID, ActionMessages.FindStringsToExternalizeAction_button_label, true); 
  			fOpenButton.setEnabled(false);
  			
  			//looks like a 'close' but it a 'cancel'
  			createButton(parent, IDialogConstants.CANCEL_ID, IDialogConstants.CLOSE_LABEL, false);
  		}
  
  		protected void buttonPressed(int buttonId) {
  			if (buttonId != OPEN_BUTTON_ID){
  				super.buttonPressed(buttonId);
  				return;
  			}	
  			ISelection s= getTableViewer().getSelection();
  			if (s instanceof IStructuredSelection){
  				IStructuredSelection ss= (IStructuredSelection)s;
  				if (ss.getFirstElement() instanceof NonNLSElement)
  					openWizard(((NonNLSElement)ss.getFirstElement()).cu);
  			}
  		}
  
  		private void openWizard(IJavaScriptUnit unit) {
  			try {
  				if (unit != null && unit.exists()) {
  					NLSRefactoring refactoring= NLSRefactoring.create(unit);
  					if (refactoring != null)
  						new RefactoringStarter().activate(refactoring, new ExternalizeWizard(refactoring), getShell(), ActionMessages.ExternalizeStringsAction_dialog_title, RefactoringSaveHelper.SAVE_NON_JAVA_UPDATES); 
  				}
  			} catch (JavaScriptModelException e) {
  				ExceptionHandler.handle(e, 
  					ActionMessages.ExternalizeStringsAction_dialog_title, 
  					ActionMessages.FindStringsToExternalizeAction_error_message); 
  			}
  		}
  		
  		private static LabelProvider createLabelProvider() {
  			return new JavaScriptElementLabelProvider(JavaScriptElementLabelProvider.SHOW_DEFAULT){ 
  				public String getText(Object element) {
  					NonNLSElement nlsel= (NonNLSElement)element;
  					String elementName= nlsel.cu.getResource().getFullPath().toString();
  					return Messages.format(
  						ActionMessages.FindStringsToExternalizeAction_foundStrings, 
  						new Object[] {Integer.valueOf(nlsel.count), elementName} );
  				}		
  				public Image getImage(Object element) {
  					return super.getImage(((NonNLSElement)element).cu);
  				}
  			};
  		}
  		
  		/*
  		 * @see org.eclipse.jface.window.Window#configureShell(Shell)
  		 */
  		protected void configureShell(Shell newShell) {
  			super.configureShell(newShell);
  			PlatformUI.getWorkbench().getHelpSystem().setHelp(newShell, IJavaHelpContextIds.NONNLS_DIALOG);		
  		}
  
  
  	}
  		
  	private static class NonNLSElement{
  		IJavaScriptUnit cu;
  		int count;
  		NonNLSElement(IJavaScriptUnit cu, int count){
  			this.cu= cu;
  			this.count= count;
  		}
  	}	
  
  }
  