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.Arrays;
  import java.util.Collection;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.Map;
  
  import org.eclipse.core.filebuffers.FileBuffers;
  import org.eclipse.core.filebuffers.ITextFileBuffer;
  import org.eclipse.core.filebuffers.ITextFileBufferManager;
  import org.eclipse.core.filebuffers.LocationKind;
  import org.eclipse.core.resources.IResource;
  import org.eclipse.core.resources.IWorkspaceRunnable;
  import org.eclipse.core.runtime.CoreException;
  import org.eclipse.core.runtime.IPath;
  import org.eclipse.core.runtime.IProgressMonitor;
  import org.eclipse.core.runtime.IStatus;
  import org.eclipse.core.runtime.MultiStatus;
  import org.eclipse.core.runtime.NullProgressMonitor;
  import org.eclipse.core.runtime.OperationCanceledException;
  import org.eclipse.core.runtime.Status;
  import org.eclipse.core.runtime.SubProgressMonitor;
  import org.eclipse.jface.action.IAction;
  import org.eclipse.jface.dialogs.ErrorDialog;
  import org.eclipse.jface.dialogs.IDialogConstants;
  import org.eclipse.jface.dialogs.MessageDialog;
  import org.eclipse.jface.text.DocumentRewriteSession;
  import org.eclipse.jface.text.DocumentRewriteSessionType;
  import org.eclipse.jface.text.IDocument;
  import org.eclipse.jface.text.IDocumentExtension;
  import org.eclipse.jface.text.IDocumentExtension4;
  import org.eclipse.jface.text.ITextSelection;
  import org.eclipse.jface.text.formatter.FormattingContextProperties;
  import org.eclipse.jface.text.formatter.IFormattingContext;
  import org.eclipse.jface.text.formatter.MultiPassContentFormatter;
  import org.eclipse.jface.viewers.ISelection;
  import org.eclipse.jface.viewers.IStructuredSelection;
  import org.eclipse.jface.window.Window;
  import org.eclipse.ui.IObjectActionDelegate;
  import org.eclipse.ui.IWorkbenchPart;
  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.JavaScriptModelException;
  import org.eclipse.wst.jsdt.internal.corext.util.JavaModelUtil;
  import org.eclipse.wst.jsdt.internal.corext.util.Messages;
  import org.eclipse.wst.jsdt.internal.corext.util.Resources;
  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.WorkbenchRunnableAdapter;
  import org.eclipse.wst.jsdt.internal.ui.browsing.LogicalPackage;
  import org.eclipse.wst.jsdt.internal.ui.dialogs.OptionalMessageDialog;
  import org.eclipse.wst.jsdt.internal.ui.text.comment.CommentFormattingContext;
  import org.eclipse.wst.jsdt.internal.ui.text.comment.CommentFormattingStrategy;
  import org.eclipse.wst.jsdt.internal.ui.text.java.JavaFormattingStrategy;
  import org.eclipse.wst.jsdt.internal.ui.util.ExceptionHandler;
  import org.eclipse.wst.jsdt.ui.JavaScriptUI;
  import org.eclipse.wst.jsdt.ui.text.IJavaScriptPartitions;
  
  /**
   * Formats the code of the compilation units contained in the selection.
   * <p>
   * The action is applicable to selections containing elements of
   * type <code>IJavaScriptUnit</code>, <code>IPackage
   * </code>, <code>IPackageFragmentRoot/code> and
   * <code>IJavaScriptProject</code>.
   * </p>
   * <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 FormatAllAction extends SelectionDispatchAction {
  	
  	private DocumentRewriteSession fRewriteSession;
  	
  	/* (non-Javadoc)
  	 * Class implements IObjectActionDelegate
  	 */
  	public static class ObjectDelegate implements IObjectActionDelegate {
  		private FormatAllAction fAction;
  		public void setActivePart(IAction action, IWorkbenchPart targetPart) {
  			fAction= new FormatAllAction(targetPart.getSite());
  		}
  		public void run(IAction action) {
  			fAction.run();
  		}
  		public void selectionChanged(IAction action, ISelection selection) {
  			if (fAction == null)
  				action.setEnabled(false);
  		}
  	}
  
  	/**
  	 * Creates a new <code>FormatAllAction</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 FormatAllAction(IWorkbenchSite site) {
  		super(site);
  		setText(ActionMessages.FormatAllAction_label); 
  		setToolTipText(ActionMessages.FormatAllAction_tooltip); 
  		setDescription(ActionMessages.FormatAllAction_description); 
  
  		PlatformUI.getWorkbench().getHelpSystem().setHelp(this, IJavaHelpContextIds.FORMAT_ALL);					
  	}
  	
  	
  	/* (non-Javadoc)
  	 * Method declared on SelectionDispatchAction.
  	 */
  	public void selectionChanged(ITextSelection selection) {
  		// do nothing
  	}
  	
  	/* (non-Javadoc)
  	 * Method declared on SelectionDispatchAction.
  	 */
  	public void selectionChanged(IStructuredSelection selection) {
  		setEnabled(isEnabled(selection));
  	}
  	
  	private IJavaScriptUnit[] getCompilationUnits(IStructuredSelection selection) {
  		HashSet result= new HashSet();
  		Object[] selected= selection.toArray();
  		for (int i= 0; i < selected.length; i++) {
  			try {
  				if (selected[i] instanceof IJavaScriptElement) {
  					IJavaScriptElement elem= (IJavaScriptElement) selected[i];
  					if (elem.exists()) {
  					
  						switch (elem.getElementType()) {
  							case IJavaScriptElement.TYPE:
  								if (elem.getParent().getElementType() == IJavaScriptElement.JAVASCRIPT_UNIT) {
  									result.add(elem.getParent());
  								}
  								break;						
  							case IJavaScriptElement.JAVASCRIPT_UNIT:
  								result.add(elem);
  								break;		
  							case IJavaScriptElement.PACKAGE_FRAGMENT:
  								collectCompilationUnits((IPackageFragment) elem, result);
  								break;
  							case IJavaScriptElement.PACKAGE_FRAGMENT_ROOT:
  								collectCompilationUnits((IPackageFragmentRoot) elem, result);
  								break;
  							case IJavaScriptElement.JAVASCRIPT_PROJECT:
  								IPackageFragmentRoot[] roots= ((IJavaScriptProject) elem).getPackageFragmentRoots();
  								for (int k= 0; k < roots.length; k++) {
  									collectCompilationUnits(roots[k], result);
  								}
  								break;			
  						}
  					}
  				} else if (selected[i] instanceof LogicalPackage) {
  					IPackageFragment[] packageFragments= ((LogicalPackage)selected[i]).getFragments();
  					for (int k= 0; k < packageFragments.length; k++) {
  						IPackageFragment pack= packageFragments[k];
  						if (pack.exists()) {
  							collectCompilationUnits(pack, result);
  						}
  					}
  				}
  			} catch (JavaScriptModelException e) {
  				JavaScriptPlugin.log(e);
  			}
  		}
  		return (IJavaScriptUnit[]) result.toArray(new IJavaScriptUnit[result.size()]);
  	}
  	
  	private void collectCompilationUnits(IPackageFragment pack, Collection result) throws JavaScriptModelException {
  		result.addAll(Arrays.asList(pack.getJavaScriptUnits()));
  	}
  
  	private void collectCompilationUnits(IPackageFragmentRoot root, Collection result) throws JavaScriptModelException {
  		if (root.getKind() == IPackageFragmentRoot.K_SOURCE) {
  			IJavaScriptElement[] children= root.getChildren();
  			for (int i= 0; i < children.length; i++) {
  				collectCompilationUnits((IPackageFragment) children[i], result);
  			}
  		}
  	}	
  	
  	private boolean isEnabled(IStructuredSelection selection) {
  		Object[] selected= selection.toArray();
  		for (int i= 0; i < selected.length; i++) {
  			try {
  				if (selected[i] instanceof IJavaScriptElement) {
  					IJavaScriptElement elem= (IJavaScriptElement) selected[i];
  					if (elem.exists()) {
  						switch (elem.getElementType()) {
  							case IJavaScriptElement.TYPE:
  								return elem.getParent().getElementType() == IJavaScriptElement.JAVASCRIPT_UNIT; // for browsing perspective
  							case IJavaScriptElement.JAVASCRIPT_UNIT:
  								return true;
  							case IJavaScriptElement.PACKAGE_FRAGMENT:
  							case IJavaScriptElement.PACKAGE_FRAGMENT_ROOT:
  								IPackageFragmentRoot root= (IPackageFragmentRoot) elem.getAncestor(IJavaScriptElement.PACKAGE_FRAGMENT_ROOT);
  								return (root.getKind() == IPackageFragmentRoot.K_SOURCE);
  							case IJavaScriptElement.JAVASCRIPT_PROJECT:
  								// https://bugs.eclipse.org/bugs/show_bug.cgi?id=65638
  								return true;
  						}
  					}
  				} else if (selected[i] instanceof LogicalPackage) {
  					return true;
  				}
  			} catch (JavaScriptModelException e) {
  				if (JavaModelUtil.isExceptionToBeLogged(e))
  					JavaScriptPlugin.log(e);
  			}
  		}
  		return false;
  	}
  	
  	/* (non-Javadoc)
  	 * Method declared on SelectionDispatchAction.
  	 */
  	public void run(ITextSelection selection) {
  	}
  	
  	/* (non-Javadoc)
  	 * Method declared on SelectionDispatchAction.
  	 */
  	public void run(IStructuredSelection selection) {
  		IJavaScriptUnit[] cus= getCompilationUnits(selection);
  		if (cus.length == 0) {
  			MessageDialog.openInformation(getShell(), ActionMessages.FormatAllAction_EmptySelection_title, ActionMessages.FormatAllAction_EmptySelection_description);
  			return;
  		}
  		try {
  			if (cus.length == 1) {
  				JavaScriptUI.openInEditor(cus[0]);
  			} else {
  				int returnCode= OptionalMessageDialog.open("FormatAll",  //$NON-NLS-1$
  						getShell(), 
  						ActionMessages.FormatAllAction_noundo_title, 
  						null,
  						ActionMessages.FormatAllAction_noundo_message,  
  						MessageDialog.WARNING, 		
  						new String[] {IDialogConstants.OK_LABEL, IDialogConstants.CANCEL_LABEL}, 
  						0);
  				if (returnCode != OptionalMessageDialog.NOT_SHOWN && returnCode != Window.OK )
  					return;
  			}
  		} catch (CoreException e) {
  			ExceptionHandler.handle(e, getShell(), ActionMessages.FormatAllAction_error_title, ActionMessages.FormatAllAction_error_message); 
  		}
  		runOnMultiple(cus);
  	}
  
  	private IResource[] getResources(IJavaScriptUnit[] cus) {
  		IResource[] res= new IResource[cus.length];
  		for (int i= 0; i < res.length; i++) {
  			res[i]= cus[i].getResource();
  		}
  		return res;
  	}
  
  	/**
  	 * Perform format all on the given compilation units.
  	 * @param cus The compilation units to format.
  	 */
  	public void runOnMultiple(final IJavaScriptUnit[] cus) {
  		try {
  			final MultiStatus status= new MultiStatus(JavaScriptUI.ID_PLUGIN, IStatus.OK, ActionMessages.FormatAllAction_status_description, null);
  			
  			IStatus valEditStatus= Resources.makeCommittable(getResources(cus), getShell());
  			if (valEditStatus.matches(IStatus.CANCEL)) {
  				return;
  			}
  			status.merge(valEditStatus);
  			if (!status.matches(IStatus.ERROR)) {
  				PlatformUI.getWorkbench().getProgressService().run(true, true, new WorkbenchRunnableAdapter(new IWorkspaceRunnable() {
  					public void run(IProgressMonitor monitor) {
  						doRunOnMultiple(cus, status, monitor);
  					}
  				})); // workspace lock
  			}
  			if (!status.isOK()) {
  				String title= ActionMessages.FormatAllAction_multi_status_title;
  				ErrorDialog.openError(getShell(), title, null, status);
  			}
  		} catch (InvocationTargetException e) {
  			ExceptionHandler.handle(e, getShell(), ActionMessages.FormatAllAction_error_title, ActionMessages.FormatAllAction_error_message); 
  		} catch (InterruptedException e) {
  			// Canceled by user
  		}
  	}
  	
  	private static Map getFomatterSettings(IJavaScriptProject project) {
  		return new HashMap(project.getOptions(true));
  	}
  	
  	private void doFormat(IDocument document, Map options) {
  		final IFormattingContext context = new CommentFormattingContext();
  		try {
  			context.setProperty(FormattingContextProperties.CONTEXT_PREFERENCES, options);
  			context.setProperty(FormattingContextProperties.CONTEXT_DOCUMENT, Boolean.valueOf(true));
  			
  			final MultiPassContentFormatter formatter= new MultiPassContentFormatter(IJavaScriptPartitions.JAVA_PARTITIONING, IDocument.DEFAULT_CONTENT_TYPE);
  			
  			formatter.setMasterStrategy(new JavaFormattingStrategy());
  			formatter.setSlaveStrategy(new CommentFormattingStrategy(), IJavaScriptPartitions.JAVA_DOC);
  			formatter.setSlaveStrategy(new CommentFormattingStrategy(), IJavaScriptPartitions.JAVA_SINGLE_LINE_COMMENT);
  			formatter.setSlaveStrategy(new CommentFormattingStrategy(), IJavaScriptPartitions.JAVA_MULTI_LINE_COMMENT);		
  
  			try {
  				startSequentialRewriteMode(document);
  				formatter.format(document, context);
  			} finally {
  				stopSequentialRewriteMode(document);
  			}
  		} finally {
  		    context.dispose();
  		}
      }
  
  	private void startSequentialRewriteMode(IDocument document) {
  		if (document instanceof IDocumentExtension4) {
  			IDocumentExtension4 extension= (IDocumentExtension4) document;
  			fRewriteSession= extension.startRewriteSession(DocumentRewriteSessionType.SEQUENTIAL);
  		} else if (document instanceof IDocumentExtension) {
  			IDocumentExtension extension= (IDocumentExtension) document;
  			extension.startSequentialRewrite(false);
  		}
  	}
  	
  	private void stopSequentialRewriteMode(IDocument document) {
  		if (document instanceof IDocumentExtension4) {
  			IDocumentExtension4 extension= (IDocumentExtension4) document;
  			extension.stopRewriteSession(fRewriteSession);
  		} else if (document instanceof IDocumentExtension) {
  			IDocumentExtension extension= (IDocumentExtension)document;
  			extension.stopSequentialRewrite();
  		}
  	}
  	
  	private void doRunOnMultiple(IJavaScriptUnit[] cus, MultiStatus status, IProgressMonitor monitor) throws OperationCanceledException {
  		if (monitor == null) {
  			monitor= new NullProgressMonitor();
  		}	
  		monitor.setTaskName(ActionMessages.FormatAllAction_operation_description); 
  	
  		monitor.beginTask("", cus.length * 4); //$NON-NLS-1$
  		try {
  			Map lastOptions= null;
  			IJavaScriptProject lastProject= null;
  			
  			for (int i= 0; i < cus.length; i++) {
  				IJavaScriptUnit cu= cus[i];
  				IPath path= cu.getPath();
  				if (lastProject == null || !lastProject.equals(cu.getJavaScriptProject())) {
  					lastProject= cu.getJavaScriptProject();
  					lastOptions= getFomatterSettings(lastProject);
  				}
  				if (monitor.isCanceled()) {
  					throw new OperationCanceledException();
  				}
  				if (cu.getResource().getResourceAttributes().isReadOnly()) {
  					String message= Messages.format(ActionMessages.FormatAllAction_read_only_skipped, path.toString());
  					status.add(new Status(IStatus.WARNING, JavaScriptUI.ID_PLUGIN, IStatus.WARNING, message, null));
  					continue;
  				}
  				
  				ITextFileBufferManager manager= FileBuffers.getTextFileBufferManager();
  				try {
  					try {
  						manager.connect(path, LocationKind.IFILE, new SubProgressMonitor(monitor, 1));
  		
  						monitor.subTask(path.makeRelative().toString());
  						ITextFileBuffer fileBuffer= manager.getTextFileBuffer(path, LocationKind.IFILE);
  						
  						formatCompilationUnit(fileBuffer, lastOptions);
  						
  						if (fileBuffer.isDirty() && !fileBuffer.isShared()) {
  							fileBuffer.commit(new SubProgressMonitor(monitor, 2), false);
  						} else {
  							monitor.worked(2);
  						}
  					} finally {
  						manager.disconnect(path, LocationKind.IFILE, new SubProgressMonitor(monitor, 1));
  					}
  				} catch (CoreException e) {
  					String message= Messages.format(ActionMessages.FormatAllAction_problem_accessing, new String[] { path.toString(), e.getLocalizedMessage() });
  					status.add(new Status(IStatus.WARNING, JavaScriptUI.ID_PLUGIN, IStatus.WARNING, message, e));
  				}
  			}
  		} finally {
  			monitor.done();
  		}
  	}
  	
  	private void formatCompilationUnit(final ITextFileBuffer fileBuffer, final Map options) {
  		if (fileBuffer.isShared()) {
  			getShell().getDisplay().syncExec(new Runnable() {
  				public void run() {
  					doFormat(fileBuffer.getDocument(), options);
  				}
  			});
  		} else {
  			doFormat(fileBuffer.getDocument(), options); // run in context thread
  		}
  	}
  	
  }
  