/******************************************************************************* * 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; } }