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.fix;
  
  import java.util.ArrayList;
  import java.util.HashMap;
  import java.util.Iterator;
  import java.util.LinkedList;
  import java.util.List;
  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.IFile;
  import org.eclipse.core.resources.IProject;
  import org.eclipse.core.resources.ProjectScope;
  import org.eclipse.core.runtime.Assert;
  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.NullProgressMonitor;
  import org.eclipse.core.runtime.Status;
  import org.eclipse.core.runtime.SubProgressMonitor;
  import org.eclipse.core.runtime.preferences.DefaultScope;
  import org.eclipse.core.runtime.preferences.IEclipsePreferences;
  import org.eclipse.core.runtime.preferences.InstanceScope;
  import org.eclipse.jface.dialogs.Dialog;
  import org.eclipse.jface.text.BadLocationException;
  import org.eclipse.jface.text.IDocument;
  import org.eclipse.jface.text.IDocumentExtension4;
  import org.eclipse.jface.window.Window;
  import org.eclipse.ltk.core.refactoring.Change;
  import org.eclipse.ltk.core.refactoring.CompositeChange;
  import org.eclipse.ltk.core.refactoring.IRefactoringCoreStatusCodes;
  import org.eclipse.ltk.core.refactoring.IUndoManager;
  import org.eclipse.ltk.core.refactoring.NullChange;
  import org.eclipse.ltk.core.refactoring.PerformChangeOperation;
  import org.eclipse.ltk.core.refactoring.RefactoringCore;
  import org.eclipse.ltk.core.refactoring.RefactoringStatus;
  import org.eclipse.ltk.core.refactoring.TextFileChange;
  import org.eclipse.ltk.ui.refactoring.RefactoringUI;
  import org.eclipse.swt.widgets.Shell;
  import org.eclipse.text.edits.TextEdit;
  import org.eclipse.text.edits.UndoEdit;
  import org.eclipse.ui.PlatformUI;
  import org.eclipse.wst.jsdt.core.IJavaScriptUnit;
  import org.eclipse.wst.jsdt.core.IJavaScriptProject;
  import org.eclipse.wst.jsdt.core.dom.ASTParser;
  import org.eclipse.wst.jsdt.core.dom.JavaScriptUnit;
  import org.eclipse.wst.jsdt.internal.corext.fix.CleanUpRefactoring.CleanUpChange;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.util.RefactoringASTParser;
  import org.eclipse.wst.jsdt.internal.corext.util.Messages;
  import org.eclipse.wst.jsdt.internal.ui.actions.ActionUtil;
  import org.eclipse.wst.jsdt.internal.ui.fix.ICleanUp;
  import org.eclipse.wst.jsdt.internal.ui.javaeditor.ASTProvider;
  import org.eclipse.wst.jsdt.internal.ui.javaeditor.saveparticipant.IPostSaveListener;
  import org.eclipse.wst.jsdt.ui.JavaScriptUI;
  
  public class CleanUpPostSaveListener implements IPostSaveListener {
  	
  	private static class CleanUpSaveUndo extends TextFileChange {
  
  		private final IFile fFile;
  		private final UndoEdit[] fUndos;
  		private final long fDocumentStamp;
  		private final long fFileStamp;
  
  		public CleanUpSaveUndo(String name, IFile file, UndoEdit[] undos, long documentStamp, long fileStamp) {
  			super(name, file);
  			Assert.isNotNull(undos);
  			
  			fDocumentStamp= documentStamp;
  			fFileStamp= fileStamp;
  			fFile= file;
  			fUndos= undos;
  		}
  
  		public final boolean needsSaving() {
  			return true;
  		}
  
  		/**
  		 * {@inheritDoc}
  		 */
  		public Change perform(IProgressMonitor pm) throws CoreException {
  			if (isValid(pm).hasFatalError())
  				return new NullChange();
  			
  			if (pm == null)
  				pm= new NullProgressMonitor();
  			
  			ITextFileBufferManager manager= FileBuffers.getTextFileBufferManager();
  			pm.beginTask("", 2); //$NON-NLS-1$
  			ITextFileBuffer buffer= null;
  			try {
  				manager.connect(fFile.getFullPath(), LocationKind.IFILE, new SubProgressMonitor(pm, 1));
  				buffer= manager.getTextFileBuffer(fFile.getFullPath(), LocationKind.IFILE);
  				IDocument document= buffer.getDocument();
  				
  				long oldFileValue= fFile.getModificationStamp();
  				long oldDocValue;
  				if (document instanceof IDocumentExtension4) {
  					oldDocValue= ((IDocumentExtension4)document).getModificationStamp();
  				} else {
  					oldDocValue= oldFileValue;
  				}
  
  				// perform the changes
  				LinkedList list= new LinkedList();
  				for (int index= 0; index < fUndos.length; index++) {
  					UndoEdit edit= fUndos[index];
  					UndoEdit redo= edit.apply(document, TextEdit.CREATE_UNDO);
  					list.addFirst(redo);
  				}
  
  				boolean stampSetted= false;
  				
  				if (document instanceof IDocumentExtension4 && fDocumentStamp != IDocumentExtension4.UNKNOWN_MODIFICATION_STAMP) {
  					try {
  						((IDocumentExtension4)document).replace(0, 0, "", fDocumentStamp); //$NON-NLS-1$
  						stampSetted= true;
  					} catch (BadLocationException e) {
  						String message= e.getMessage();
                          if (message == null)
                          	message= "BadLocationException"; //$NON-NLS-1$
  						throw new CoreException(new Status(IStatus.ERROR, JavaScriptUI.ID_PLUGIN, IRefactoringCoreStatusCodes.BAD_LOCATION, message, e));
  					}
  				}
  				
  				buffer.commit(pm, false);
  				if (!stampSetted) {
  					fFile.revertModificationStamp(fFileStamp);
  				}
  				
  				return new CleanUpSaveUndo(getName(), fFile, ((UndoEdit[]) list.toArray(new UndoEdit[list.size()])), oldDocValue, oldFileValue);
  			} catch (BadLocationException e) {
  				String message= e.getMessage();
                  if (message == null)
                  	message= "BadLocationException"; //$NON-NLS-1$
  				throw new CoreException(new Status(IStatus.ERROR, JavaScriptUI.ID_PLUGIN, IRefactoringCoreStatusCodes.BAD_LOCATION, message, e));
  			} finally {
  				if (buffer != null)
  					manager.disconnect(fFile.getFullPath(), LocationKind.IFILE, new SubProgressMonitor(pm, 1));
  			}
  		}
  	}
  
  	public static final String POSTSAVELISTENER_ID= "org.eclipse.wst.jsdt.ui.postsavelistener.cleanup"; //$NON-NLS-1$
  	private static final String WARNING_VALUE= "warning"; //$NON-NLS-1$
  	private static final String ERROR_VALUE= "error"; //$NON-NLS-1$
  
  	/**
  	 * {@inheritDoc}
  	 */
  	public void saved(IJavaScriptUnit unit, IProgressMonitor monitor) throws CoreException {
  		if (monitor == null)
  			monitor= new NullProgressMonitor();
  		
  		monitor.beginTask(getName(), IProgressMonitor.UNKNOWN);
  		
  		try {
  			if (!ActionUtil.isOnBuildPath(unit))
  				return;
  			
  			IProject project= unit.getJavaScriptProject().getProject();
  			Map settings= CleanUpPreferenceUtil.loadSaveParticipantOptions(new ProjectScope(project));
  			if (settings == null) {
  				IEclipsePreferences contextNode= new InstanceScope().getNode(JavaScriptUI.ID_PLUGIN);
  				String id= contextNode.get(CleanUpConstants.CLEANUP_ON_SAVE_PROFILE, null);
  				if (id == null) {
  					id= new DefaultScope().getNode(JavaScriptUI.ID_PLUGIN).get(CleanUpConstants.CLEANUP_ON_SAVE_PROFILE, CleanUpConstants.DEFAULT_SAVE_PARTICIPANT_PROFILE);
  				}
  				throw new CoreException(new Status(IStatus.ERROR, JavaScriptUI.ID_PLUGIN, Messages.format(FixMessages.CleanUpPostSaveListener_unknown_profile_error_message, id)));
  			}
  			
  			ICleanUp[] cleanUps;
  			if (CleanUpConstants.TRUE.equals(settings.get(CleanUpConstants.CLEANUP_ON_SAVE_ADDITIONAL_OPTIONS))) {
  				cleanUps= CleanUpRefactoring.createCleanUps(settings);
  			} else {
  				HashMap filteredSettins= new HashMap();
  				filteredSettins.put(CleanUpConstants.FORMAT_SOURCE_CODE, settings.get(CleanUpConstants.FORMAT_SOURCE_CODE));
  				filteredSettins.put(CleanUpConstants.ORGANIZE_IMPORTS, settings.get(CleanUpConstants.ORGANIZE_IMPORTS));
  				cleanUps= CleanUpRefactoring.createCleanUps(filteredSettins);
  			}
  						
  			long oldFileValue= unit.getResource().getModificationStamp();
  			long oldDocValue= getDocumentStamp((IFile)unit.getResource(), new SubProgressMonitor(monitor, 2));
  			
  			CompositeChange result= new CompositeChange(FixMessages.CleanUpPostSaveListener_SaveAction_ChangeName);
  			LinkedList undoEdits= new LinkedList();
  			
  			IUndoManager manager= RefactoringCore.getUndoManager();
  			
  			try {
      			manager.aboutToPerformChange(result);
      			
      			do {
      				RefactoringStatus preCondition= new RefactoringStatus();
      				for (int i= 0; i < cleanUps.length; i++) {
      					RefactoringStatus conditions= cleanUps[i].checkPreConditions(unit.getJavaScriptProject(), new IJavaScriptUnit[] {unit}, new SubProgressMonitor(monitor, 5));
      					preCondition.merge(conditions);
      				}
      				if (showStatus(preCondition) != Window.OK)
      					return;
      				
      				Map options= new HashMap();
      				for (int i= 0; i < cleanUps.length; i++) {
      					Map map= cleanUps[i].getRequiredOptions();
      					if (map != null) {
      						options.putAll(map);
      					}
      				}
      					
      				JavaScriptUnit ast= null;
      				if (requiresAST(cleanUps, unit)) {
      					ast= createAst(unit, options, new SubProgressMonitor(monitor, 10));
      				}
      				
      				List undoneCleanUps= new ArrayList();
      				CleanUpChange change= CleanUpRefactoring.calculateChange(ast, unit, cleanUps, undoneCleanUps);
      				
      				RefactoringStatus postCondition= new RefactoringStatus();
      				for (int i= 0; i < cleanUps.length; i++) {
      					RefactoringStatus conditions= cleanUps[i].checkPostConditions(new SubProgressMonitor(monitor, 1));
      					postCondition.merge(conditions);
      				}
      				if (showStatus(postCondition) != Window.OK)
      					return;
      				
      				if (change != null) {
      					result.add(change);
      					
      					change.setSaveMode(TextFileChange.LEAVE_DIRTY);
      					change.initializeValidationData(new NullProgressMonitor());
      					
      					PerformChangeOperation performChangeOperation= RefactoringUI.createUIAwareChangeOperation(change);
      					performChangeOperation.setSchedulingRule(unit.getSchedulingRule());
      					
      					performChangeOperation.run(new SubProgressMonitor(monitor, 5));
      					
      					performChangeOperation.getUndoChange();
      					undoEdits.addFirst(change.getUndoEdit());
      				}
      				
      				cleanUps= (ICleanUp[])undoneCleanUps.toArray(new ICleanUp[undoneCleanUps.size()]);
      			} while (cleanUps.length > 0);
  			} finally {
  				manager.changePerformed(result, true);
  			}
  			
  			if (undoEdits.size() > 0) {
      			UndoEdit[] undoEditArray= (UndoEdit[])undoEdits.toArray(new UndoEdit[undoEdits.size()]);
      			CleanUpSaveUndo undo= new CleanUpSaveUndo(result.getName(), (IFile)unit.getResource(), undoEditArray, oldDocValue, oldFileValue);
      			undo.initializeValidationData(new NullProgressMonitor());
      			manager.addUndo(result.getName(), undo);
  			}
  		} finally {
  			monitor.done();
  		}
  	}
  
  	private int showStatus(RefactoringStatus status) {
  		if (!status.hasError())
  			return Window.OK;
  
  		Shell shell= PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell();
  		
  		Dialog dialog= RefactoringUI.createRefactoringStatusDialog(status, shell, "", false); //$NON-NLS-1$
  		return dialog.open(); 
      }
  
  	private long getDocumentStamp(IFile file, IProgressMonitor monitor) throws CoreException {
  	    final ITextFileBufferManager manager= FileBuffers.getTextFileBufferManager();
  	    final IPath path= file.getFullPath();
  
  	    monitor.beginTask("", 2); //$NON-NLS-1$
  	    
  	    ITextFileBuffer buffer= null;
  	    try {
  	    	manager.connect(path, LocationKind.IFILE, new SubProgressMonitor(monitor, 1));
  		    buffer= manager.getTextFileBuffer(path, LocationKind.IFILE);
      	    IDocument document= buffer.getDocument();
      	    
      	    if (document instanceof IDocumentExtension4) {
      			return ((IDocumentExtension4)document).getModificationStamp();
      		} else {
      			return file.getModificationStamp();
      		}
  	    } finally {
  	    	if (buffer != null)
  	    		manager.disconnect(path, LocationKind.IFILE, new SubProgressMonitor(monitor, 1));
  	    	monitor.done();
  	    }
      }
  
  	private boolean requiresAST(ICleanUp[] cleanUps, IJavaScriptUnit unit) throws CoreException {
  		for (int i= 0; i < cleanUps.length; i++) {
  	        if (cleanUps[i].requireAST(unit))
  	        	return true;
          }
  		
  	    return false;
      }
  
  	private JavaScriptUnit createAst(IJavaScriptUnit unit, Map cleanUpOptions, IProgressMonitor monitor) {
  		IJavaScriptProject project= unit.getJavaScriptProject();
  		if (compatibleOptions(project, cleanUpOptions)) {
  			JavaScriptUnit ast= ASTProvider.getASTProvider().getAST(unit, ASTProvider.WAIT_NO, monitor);
  			if (ast != null)
  				return ast;
  		}
  		
  		ASTParser parser= ASTParser.newParser(ASTProvider.SHARED_AST_LEVEL);
  		parser.setResolveBindings(true);
  		parser.setProject(project);
  		parser.setSource(unit);
  		Map compilerOptions= RefactoringASTParser.getCompilerOptions(unit.getJavaScriptProject());
  		compilerOptions.putAll(cleanUpOptions);
  		parser.setCompilerOptions(compilerOptions);
  		
  		return (JavaScriptUnit)parser.createAST(monitor);
  	}
  	
  	private boolean compatibleOptions(IJavaScriptProject project, Map cleanUpOptions) {
  		if (cleanUpOptions.size() == 0)
  			return true;
  		
  		Map projectOptions= project.getOptions(true);
  		
  		for (Iterator iterator= cleanUpOptions.keySet().iterator(); iterator.hasNext();) {
  	        String key= (String)iterator.next();
  	        String projectOption= (String)projectOptions.get(key);
  			String cleanUpOption= (String)cleanUpOptions.get(key);
  			if (!strongerEquals(projectOption, cleanUpOption))
  				return false;
          }
  		
  	    return true;
      }
  
  	private boolean strongerEquals(String projectOption, String cleanUpOption) {
  		if (projectOption == null)
  			return false;
  		
  		if (ERROR_VALUE.equals(cleanUpOption)) {
  			return ERROR_VALUE.equals(projectOption);
  		} else if (WARNING_VALUE.equals(cleanUpOption)) {
  			return ERROR_VALUE.equals(projectOption) || WARNING_VALUE.equals(projectOption);
  		}
  		
  	    return false;
      }
  
  	/**
  	 * {@inheritDoc}
  	 */
  	public String getName() {
  		return FixMessages.CleanUpPostSaveListener_name;
  	}
  	
  	/**
  	 * {@inheritDoc}
  	 */
  	public String getId() {
  		return POSTSAVELISTENER_ID;
  	}
  	
  }
  