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.ui.text.correction;
  
  
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.Hashtable;
  import java.util.Iterator;
  import java.util.List;
  import java.util.Set;
  
  import org.eclipse.core.resources.IFile;
  import org.eclipse.core.resources.IMarker;
  import org.eclipse.core.resources.IResource;
  import org.eclipse.core.runtime.CoreException;
  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.Status;
  import org.eclipse.core.runtime.SubProgressMonitor;
  import org.eclipse.jface.dialogs.ErrorDialog;
  import org.eclipse.jface.text.IDocument;
  import org.eclipse.jface.text.Position;
  import org.eclipse.jface.text.source.IAnnotationModel;
  import org.eclipse.ltk.core.refactoring.Change;
  import org.eclipse.ltk.core.refactoring.CompositeChange;
  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.RefactoringStatusEntry;
  import org.eclipse.ltk.core.refactoring.TextChange;
  import org.eclipse.swt.graphics.Image;
  import org.eclipse.ui.IEditorInput;
  import org.eclipse.ui.IEditorPart;
  import org.eclipse.ui.IMarkerResolution;
  import org.eclipse.ui.IMarkerResolutionGenerator;
  import org.eclipse.ui.IMarkerResolutionGenerator2;
  import org.eclipse.ui.texteditor.ITextEditor;
  import org.eclipse.ui.texteditor.MarkerUtilities;
  import org.eclipse.ui.views.markers.WorkbenchMarkerResolution;
  import org.eclipse.wst.jsdt.core.CorrectionEngine;
  import org.eclipse.wst.jsdt.core.IJavaScriptUnit;
  import org.eclipse.wst.jsdt.core.IJavaScriptElement;
  import org.eclipse.wst.jsdt.core.IJavaScriptModelMarker;
  import org.eclipse.wst.jsdt.core.IJavaScriptProject;
  import org.eclipse.wst.jsdt.core.JavaScriptCore;
  import org.eclipse.wst.jsdt.core.JavaScriptModelException;
  import org.eclipse.wst.jsdt.core.compiler.IProblem;
  import org.eclipse.wst.jsdt.core.dom.ASTParser;
  import org.eclipse.wst.jsdt.core.dom.ASTRequestor;
  import org.eclipse.wst.jsdt.core.dom.JavaScriptUnit;
  import org.eclipse.wst.jsdt.internal.corext.fix.IFix;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.Checks;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.changes.CompilationUnitChange;
  import org.eclipse.wst.jsdt.internal.corext.refactoring.changes.MultiStateCompilationUnitChange;
  import org.eclipse.wst.jsdt.internal.ui.JavaScriptPlugin;
  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.EditorUtility;
  import org.eclipse.wst.jsdt.internal.ui.javaeditor.JavaMarkerAnnotation;
  import org.eclipse.wst.jsdt.ui.JavaScriptUI;
  import org.eclipse.wst.jsdt.ui.text.java.CompletionProposalComparator;
  import org.eclipse.wst.jsdt.ui.text.java.IInvocationContext;
  import org.eclipse.wst.jsdt.ui.text.java.IJavaCompletionProposal;
  import org.eclipse.wst.jsdt.ui.text.java.IProblemLocation;
  
  /**
    */
  public class CorrectionMarkerResolutionGenerator implements IMarkerResolutionGenerator, IMarkerResolutionGenerator2 {
  
  	public static class CorrectionMarkerResolution extends WorkbenchMarkerResolution {
  
  		private static final IMarker[] NO_MARKERS= new IMarker[0];
  		private static final int BATCH_SIZE= 40;
  
  		private IJavaScriptUnit fCompilationUnit;
  		private int fOffset;
  		private int fLength;
  		private IJavaCompletionProposal fProposal;
  		private final IMarker fMarker;
  
  		/**
  		 * Constructor for CorrectionMarkerResolution.
  		 * @param marker 
  		 */
  		public CorrectionMarkerResolution(IJavaScriptUnit cu, int offset, int length, IJavaCompletionProposal proposal, IMarker marker) {
  			fCompilationUnit= cu;
  			fOffset= offset;
  			fLength= length;
  			fProposal= proposal;
  			fMarker= marker;
  		}
  
  		/* (non-Javadoc)
  		 * @see IMarkerResolution#getLabel()
  		 */
  		public String getLabel() {
  			return fProposal.getDisplayString();
  		}
  
  		/* (non-Javadoc)
  		 * @see IMarkerResolution#run(IMarker)
  		 */
  		public void run(IMarker marker) {
  			try {
  				IEditorPart part= EditorUtility.isOpenInEditor(fCompilationUnit);
  				if (part == null) {
  					part= JavaScriptUI.openInEditor(fCompilationUnit, true, false);
  					if (part instanceof ITextEditor) {
  						((ITextEditor) part).selectAndReveal(fOffset, fLength);
  					}
  				}
  				if (part != null) {
  					IEditorInput input= part.getEditorInput();
  					IDocument doc= JavaScriptPlugin.getDefault().getCompilationUnitDocumentProvider().getDocument(input);					
  					fProposal.apply(doc);
  				}
  			} catch (CoreException e) {
  				JavaScriptPlugin.log(e);
  			}
  		}
  		
  		public void run(IMarker[] markers, IProgressMonitor monitor) {
  			if (markers.length == 1) {
  				run(markers[0]);
  				return;
  			}
  			IProgressMonitor pm= monitor;
  			if (pm == null)
  				pm= new NullProgressMonitor();
  			
  			try {
  				if (fProposal instanceof FixCorrectionProposal) {
  					ICleanUp cleanUp= ((FixCorrectionProposal)fProposal).getCleanUp();
  					if (cleanUp != null) {
  						Hashtable/*<IJavaScriptUnit, List<IProblemLocation>*/ problemLocations= new Hashtable();
  						for (int i= 0; i < markers.length; i++) {
  							IMarker marker= markers[i];
  							IJavaScriptUnit cu= getCompilationUnit(marker);
  							
  							if (cu != null) {
  								try {
  									IEditorInput input= EditorUtility.getEditorInput(cu);
  									IProblemLocation location= findProblemLocation(input, marker);
  									if (location != null) {
  										if (!problemLocations.containsKey(cu.getPrimary())) {
  											problemLocations.put(cu.getPrimary(), new ArrayList());
  										}
  										List l= (List)problemLocations.get(cu.getPrimary());
  										l.add(location);
  									}
  								} catch (JavaScriptModelException e) {
  									JavaScriptPlugin.log(e);
  								}
  							}
  						}
  						if (problemLocations.size() > 0) {
  									
  							Set cus= problemLocations.keySet();
  							Hashtable projects= new Hashtable();
  							for (Iterator iter= cus.iterator(); iter.hasNext();) {
  								IJavaScriptUnit cu= (IJavaScriptUnit)iter.next();
  								IJavaScriptProject project= cu.getJavaScriptProject();
  								if (!projects.containsKey(project)) {
  									projects.put(project, new ArrayList());
  								}
  								((List)projects.get(project)).add(cu);
  							}
  							
  							pm.beginTask("", problemLocations.size() * 2 + 2 + projects.keySet().size()); //$NON-NLS-1$
  							
  							String name= ""; //$NON-NLS-1$
  							String[] descriptions= cleanUp.getDescriptions();
  							if (descriptions != null && descriptions.length == 1) {
  								name= descriptions[0];
  							}
  							CompositeChange allChanges= new CompositeChange(name);
  							
  							for (Iterator projectIter= projects.keySet().iterator(); projectIter.hasNext();) {
  								IJavaScriptProject project= (IJavaScriptProject)projectIter.next();
  								List compilationUnitsList= (List)projects.get(project);
  								IJavaScriptUnit[] compilationUnits= (IJavaScriptUnit[])compilationUnitsList.toArray(new IJavaScriptUnit[compilationUnitsList.size()]);
  								
  								try {
  									cleanUpProject(project, compilationUnits, cleanUp, problemLocations, allChanges, pm);
  								} catch (CoreException e) {
  									JavaScriptPlugin.log(e);
  								} finally {
  									pm.worked(1);
  								}
  							}
  
  							if (pm.isCanceled())
  								return;
  							
  							allChanges.initializeValidationData(new SubProgressMonitor(pm, 1));
  							
  							if (!validChanges(allChanges))
  								return;
  							
  							PerformChangeOperation op= new PerformChangeOperation(allChanges);
  							op.setUndoManager(RefactoringCore.getUndoManager(), allChanges.getName());
  							try {
  								op.run(new SubProgressMonitor(pm, 1));
  							} catch (CoreException e1) {
  								JavaScriptPlugin.log(e1);
  							} finally {
  								pm.worked(1);
  							}
  							IEditorPart part= EditorUtility.isOpenInEditor(fCompilationUnit);
  							if (part instanceof ITextEditor) {
  								((ITextEditor) part).selectAndReveal(fOffset, fLength);
  								part.setFocus();
  							}
  						}
  					}
  				} 
  			} finally {
  				pm.done();
  			}
  		}
  
  		private boolean validChanges(CompositeChange change) {
  			RefactoringStatus result= new RefactoringStatus();
  			List files= new ArrayList();
  			try {
  				findFilesToBeModified(change, files);
  			} catch (JavaScriptModelException e) {
  				JavaScriptPlugin.log(e);
  				return false;
  			}
  			result.merge(Checks.validateModifiesFiles((IFile[])files.toArray(new IFile[files.size()]), JavaScriptPlugin.getActiveWorkbenchShell().getShell()));
  			if (result.hasFatalError()) {
  				RefactoringStatusEntry[] entries= result.getEntries();
  				IStatus status;
  				if (entries.length > 1) {
  					status= new MultiStatus(JavaScriptUI.ID_PLUGIN, 0, result.getMessageMatchingSeverity(RefactoringStatus.ERROR), null);
  					for (int i= 0; i < entries.length; i++) {
  						((MultiStatus)status).add(new Status(entries[i].getSeverity(), JavaScriptUI.ID_PLUGIN, 0, entries[i].getMessage(), null));
  					}
  				} else {
  					RefactoringStatusEntry entry= entries[0];
  					status= new Status(entry.getSeverity(), JavaScriptUI.ID_PLUGIN, 0, entry.getMessage(), null);
  				}
  				ErrorDialog.openError(JavaScriptPlugin.getActiveWorkbenchShell().getShell(), CorrectionMessages.CorrectionMarkerResolutionGenerator__multiFixErrorDialog_Titel, CorrectionMessages.CorrectionMarkerResolutionGenerator_multiFixErrorDialog_description, status);
  				return false;
  			}
  			return true;
  		}
  		
  		private void findFilesToBeModified(CompositeChange change, List result) throws JavaScriptModelException {
  			Change[] children= change.getChildren();
  			for (int i=0;i < children.length;i++) {
  				Change child= children[i];
  				if (child instanceof CompositeChange) {
  					findFilesToBeModified((CompositeChange)child, result);
  				} else if (child instanceof MultiStateCompilationUnitChange) {
  					result.add(((MultiStateCompilationUnitChange)child).getCompilationUnit().getCorrespondingResource());
  				} else if (child instanceof CompilationUnitChange) {
  					result.add(((CompilationUnitChange)child).getCompilationUnit().getCorrespondingResource());
  				}
  			}
  		}
  
  		private void cleanUpProject(IJavaScriptProject project, IJavaScriptUnit[] compilationUnits, ICleanUp cleanUp, Hashtable problemLocations, CompositeChange result, IProgressMonitor monitor) throws CoreException {
  			cleanUp.checkPreConditions(project, compilationUnits, new SubProgressMonitor(monitor, 1));
  			for (int i= 0; i < compilationUnits.length; i++) {
  				IJavaScriptUnit cu= compilationUnits[i];
  				JavaScriptUnit root= getASTRoot(cu, new SubProgressMonitor(monitor, 1));
  				List locationList= (List)problemLocations.get(cu);
  				IProblemLocation[] locations= (IProblemLocation[])locationList.toArray(new IProblemLocation[locationList.size()]);
  
  				IFix fix= cleanUp.createFix(root, locations);
  				
  				if (monitor.isCanceled())
  					return;
  				
  				if (fix != null) {
  					TextChange change= fix.createChange();
  					
  					if (monitor.isCanceled())
  						return;
  					
  					result.add(change);
  					monitor.worked(1);
  				}
  
  			}
  			cleanUp.checkPostConditions(null);	
  		}
  
  		/* (non-Javadoc)
  		 * @see org.eclipse.ui.IMarkerResolution2#getDescription()
  		 */
  		public String getDescription() {
  			return fProposal.getAdditionalProposalInfo();
  		}
  
  		/* (non-Javadoc)
  		 * @see org.eclipse.ui.IMarkerResolution2#getImage()
  		 */
  		public Image getImage() {
  			return fProposal.getImage();
  		}
  		
  		/**
  		 * {@inheritDoc}
  		 */
  		public IMarker[] findOtherMarkers(IMarker[] markers) {
  			if (!(fProposal instanceof FixCorrectionProposal))
  				return NO_MARKERS;
  			
  			FixCorrectionProposal fix= (FixCorrectionProposal)fProposal;
  			final ICleanUp cleanUp= fix.getCleanUp();
  			if (cleanUp == null) 
  				return NO_MARKERS;
  			
  			final Hashtable fileMarkerTable= getMarkersForFiles(markers);
  			if (fileMarkerTable.isEmpty())
  				return NO_MARKERS;
  			
  			Hashtable projectICUTable= getCompilationUnitsForProjects(fileMarkerTable);
  			if (projectICUTable.size() == 0)
  				return NO_MARKERS;
  			
  			final List result= new ArrayList();
  			
  			for (Iterator iter= projectICUTable.keySet().iterator(); iter.hasNext();) {
  				IJavaScriptProject project= (IJavaScriptProject)iter.next();
  				List cus= (List)projectICUTable.get(project);
  				ASTParser parser= getParser(project);
  				
  				int start= 0;
  				int end= 0;
  				while (end < cus.size()) {
  					end= Math.min(start + BATCH_SIZE, cus.size());
  				
  					List toParse= cus.subList(start, end);
  					IJavaScriptUnit[] units= (IJavaScriptUnit[])toParse.toArray(new IJavaScriptUnit[toParse.size()]);
  					parser.createASTs(units, new String[0], new ASTRequestor() {
  						/**
  						 * {@inheritDoc}
  						 */
  						public void acceptAST(IJavaScriptUnit cu, JavaScriptUnit root) {
  							try {
  								IEditorInput input= EditorUtility.getEditorInput(cu);
  								
  								List fileMarkers= (List)fileMarkerTable.get(cu.getResource());
  								
  								for (Iterator iterator= fileMarkers.iterator(); iterator.hasNext();) {
  									IMarker marker= (IMarker)iterator.next();
  									IProblemLocation location= findProblemLocation(input, marker);
  									if (location != null) {
  										if (cleanUp.canFix(root, location)) {
  											result.add(marker);
  										}
  									}						
  								}
  							} catch (CoreException e) {
  								JavaScriptPlugin.log(e);
  							}
  						}
  					}, new NullProgressMonitor());
  					
  					start= end;
  				}
  			}	
  			if (result.size() == 0)
  				return NO_MARKERS;
  			
  			return (IMarker[])result.toArray(new IMarker[result.size()]);
  		}
  
  		/**
  		 * Returns the markers with the same type as fMarker.getType for each IFile.
  		 */
  		private Hashtable/*<IFile, List<IMarker>>*/ getMarkersForFiles(IMarker[] markers) {
  			final Hashtable result= new Hashtable();
  			
  			String markerType;
  			try {
  				markerType= fMarker.getType();
  			} catch (CoreException e1) {
  				JavaScriptPlugin.log(e1);
  				return result;
  			}
  			
  			for (int i= 0; i < markers.length; i++) {
  				IMarker marker= markers[i];
  				if (!marker.equals(fMarker)) {
  					String currMarkerType= null;
  					try {
  						currMarkerType= marker.getType();
  					} catch (CoreException e1) {
  						JavaScriptPlugin.log(e1);
  					}
  				
  					if (currMarkerType != null && currMarkerType.equals(markerType)) {
  						IResource res= marker.getResource();
  						if (res instanceof IFile && res.isAccessible()) {
  							List markerList= (List)result.get(res);
  							if (markerList == null) {
  								markerList= new ArrayList();
  								result.put(res, markerList);
  							}
  							markerList.add(marker);
  						}
  					}
  				}
  			}
  			return result;
  		}
  		
  		/**
  		 * Returns the ICompilationUnits for each IJavaScriptProject 
  		 */
  		private Hashtable/*<IJavaScriptProject, List<IJavaScriptUnit>>*/ getCompilationUnitsForProjects(final Hashtable/*<IFile, List<IMarker>>*/ fileMarkerTable) {
  			Hashtable result= new Hashtable();
  			for (Iterator iter= fileMarkerTable.keySet().iterator(); iter.hasNext();) {
  				IFile res= (IFile)iter.next();
  				IJavaScriptElement element= JavaScriptCore.create(res);
  				
  				if (element instanceof IJavaScriptUnit) {
  					IJavaScriptUnit cu= (IJavaScriptUnit)element;
  					List cus= (List)result.get(cu.getJavaScriptProject());
  					if (cus == null) {
  						cus= new ArrayList();
  						result.put(cu.getJavaScriptProject(), cus);
  					}
  					cus.add(cu);
  				}
  			}
  			return result;
  		}
  
  		private static ASTParser getParser(IJavaScriptProject javaProject) {
  			ASTParser parser= ASTParser.newParser(ASTProvider.SHARED_AST_LEVEL);
  			parser.setResolveBindings(true);
  			parser.setProject(javaProject);
  			return parser;
  		}
  		
  		private static JavaScriptUnit getASTRoot(IJavaScriptUnit compilationUnit, IProgressMonitor monitor) {
  			JavaScriptUnit result= ASTProvider.getASTProvider().getAST(compilationUnit, ASTProvider.WAIT_YES, monitor);
  			if (result == null) {
  				// see bug 63554
  				result= ASTResolving.createQuickFixAST(compilationUnit, monitor);
  			}
  			return result;
  		}
  	}
  
  	private static final IMarkerResolution[] NO_RESOLUTIONS= new IMarkerResolution[0];
  
  
  	/**
  	 * Constructor for CorrectionMarkerResolutionGenerator.
  	 */
  	public CorrectionMarkerResolutionGenerator() {
  		super();
  	}
  
  	/* (non-Javadoc)
  	 * @see org.eclipse.ui.IMarkerResolutionGenerator2#hasResolutions(org.eclipse.core.resources.IMarker)
  	 */
  	public boolean hasResolutions(IMarker marker) {
  		return internalHasResolutions(marker);
  	}
  
  	/* (non-Javadoc)
  	 * @see IMarkerResolutionGenerator#getResolutions(IMarker)
  	 */
  	public IMarkerResolution[] getResolutions(IMarker marker) {
  		return internalGetResolutions(marker);
  	}
  	
  	private static boolean internalHasResolutions(IMarker marker) {
  		int id= marker.getAttribute(IJavaScriptModelMarker.ID, -1);
  		IJavaScriptUnit cu= getCompilationUnit(marker);
  		return cu != null && JavaCorrectionProcessor.hasCorrections(cu, id, MarkerUtilities.getMarkerType(marker));
  	}
  	
  	private static IMarkerResolution[] internalGetResolutions(IMarker marker) {
  		if (!internalHasResolutions(marker)) {
  			return NO_RESOLUTIONS;
  		}
  
  		try {
  			IJavaScriptUnit cu= getCompilationUnit(marker);
  			if (cu != null) {
  				IEditorInput input= EditorUtility.getEditorInput(cu);
  				if (input != null) {
  					IProblemLocation location= findProblemLocation(input, marker);
  					if (location != null) {
  
  						IInvocationContext context= new AssistContext(cu,  location.getOffset(), location.getLength());
  						if (!hasProblem (context.getASTRoot().getProblems(), location)) 
  							return NO_RESOLUTIONS;
  						
  						ArrayList proposals= new ArrayList();
  						JavaCorrectionProcessor.collectCorrections(context, new IProblemLocation[] { location }, proposals);
  						Collections.sort(proposals, new CompletionProposalComparator());
  
  						int nProposals= proposals.size();
  						IMarkerResolution[] resolutions= new IMarkerResolution[nProposals];
  						for (int i= 0; i < nProposals; i++) {
  							resolutions[i]= new CorrectionMarkerResolution(context.getCompilationUnit(), location.getOffset(), location.getLength(), (IJavaCompletionProposal) proposals.get(i), marker);
  						}
  						return resolutions;
  					}
  				}
  			}
  		} catch (JavaScriptModelException e) {
  			JavaScriptPlugin.log(e);
  		}
  		return NO_RESOLUTIONS;
  	}
  
  	private static boolean hasProblem(IProblem[] problems, IProblemLocation location) {
  		for (int i= 0; i < problems.length; i++) {
  			IProblem problem= problems[i];
  			if (problem.getID() == location.getProblemId() && problem.getSourceStart() == location.getOffset())
  				return true;
  		}
  		return false;
  	}
  
  	private static IJavaScriptUnit getCompilationUnit(IMarker marker) {
  		IResource res= marker.getResource();
  		if (res instanceof IFile && res.isAccessible()) {
  			IJavaScriptElement element= JavaScriptCore.create((IFile) res);
  			if (element instanceof IJavaScriptUnit)
  				return (IJavaScriptUnit) element;
  		}
  		return null;
  	}
  
  	private static IProblemLocation findProblemLocation(IEditorInput input, IMarker marker) {
  		IAnnotationModel model= JavaScriptPlugin.getDefault().getCompilationUnitDocumentProvider().getAnnotationModel(input);
  		if (model != null) { // open in editor
  			Iterator iter= model.getAnnotationIterator();
  			while (iter.hasNext()) {
  				Object curr= iter.next();
  				if (curr instanceof JavaMarkerAnnotation) {
  					JavaMarkerAnnotation annot= (JavaMarkerAnnotation) curr;
  					if (marker.equals(annot.getMarker())) {
  						Position pos= model.getPosition(annot);
  						if (pos != null) {
  							return new ProblemLocation(pos.getOffset(), pos.getLength(), annot);
  						}
  					}
  				}
  			}
  		} else { // not open in editor
  			IJavaScriptUnit cu= getCompilationUnit(marker);
  			return createFromMarker(marker, cu);
  		}
  		return null;
  	}
  
  	private static IProblemLocation createFromMarker(IMarker marker, IJavaScriptUnit cu) {
  		try {
  			int id= marker.getAttribute(IJavaScriptModelMarker.ID, -1);
  			int start= marker.getAttribute(IMarker.CHAR_START, -1);
  			int end= marker.getAttribute(IMarker.CHAR_END, -1);
  			int severity= marker.getAttribute(IMarker.SEVERITY, IMarker.SEVERITY_INFO);
  			String[] arguments= CorrectionEngine.getProblemArguments(marker);
  			String markerType= marker.getType();
  			if (cu != null && id != -1 && start != -1 && end != -1 && arguments != null) {
  				boolean isError= (severity == IMarker.SEVERITY_ERROR);
  				return new ProblemLocation(start, end - start, id, arguments, isError, markerType);
  			}
  		} catch (CoreException e) {
  			JavaScriptPlugin.log(e);
  		}
  		return null;
  	}
  	
  
  }
  