/*******************************************************************************
* Copyright (c) 2010 Thiago Tonelli Bartolomei.
* 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:
* Thiago Tonelli Bartolomei - initial API and implementation
******************************************************************************/
package ca.uwaterloo.gsd.fsml.javaMappingInterpreter.analysis.impl;
import java.util.HashMap;
import java.util.Map.Entry;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Status;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.ITypeRoot;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.compiler.IProblem;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.refactoring.CompilationUnitChange;
import org.eclipse.jdt.internal.core.dom.rewrite.TrackedNodePosition;
import org.eclipse.jdt.internal.ui.JavaPlugin;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IRewriteTarget;
import org.eclipse.ltk.core.refactoring.Change;
import org.eclipse.ltk.core.refactoring.IUndoManager;
import org.eclipse.ltk.core.refactoring.RefactoringCore;
import org.eclipse.ltk.core.refactoring.RefactoringStatus;
import org.eclipse.ltk.core.refactoring.TextChange;
import org.eclipse.ltk.core.refactoring.TextFileChange;
import org.eclipse.text.edits.InsertEdit;
import org.eclipse.text.edits.MalformedTreeException;
import org.eclipse.text.edits.MultiTextEdit;
import org.eclipse.text.edits.TextEdit;
import org.eclipse.text.edits.TextEditGroup;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.PlatformUI;
import ca.uwaterloo.gsd.fsml.javaMappingInterpreter.CodeTransforms;
import ca.uwaterloo.gsd.fsml.javaMappingInterpreter.JavaMappingInterpreter;
import ca.uwaterloo.gsd.fsml.javaMappingInterpreter.analysis.IJavaASTManager;
import ca.uwaterloo.gsd.fsml.javaMappingInterpreter.analysis.JavaModelUtils;
import ca.uwaterloo.gsd.fsml.stats.Stats;
/**
* A Manager for Java ASTs that were parsed during analysis.
*
* @author Thiago Tonelli Bartolomei <ttonelli@gsd.uwaterloo.ca>
* @author Michal Antkiewicz <mantkiew@gsd.uwaterloo.ca>
*/
public class JavaASTManager implements IJavaASTManager {
/**
* maps original type roots (not working copies!) to their parse trees
*/
protected HashMap<ITypeRoot, CompilationUnit> iTypeRoot2CompilationUnit = new HashMap<ITypeRoot, CompilationUnit>();
protected ASTParser parser = null;
protected IJavaProject project = null;
private IProgressMonitor progressMonitor;
/**
* Creates a new cache for this java project
*
* @param project the java project
*/
public JavaASTManager(IJavaProject project) {
this.project = project;
this.progressMonitor = new NullProgressMonitor();
}
/**
* Gets the parser, creating it if needed.
*
* @return the AST parser
*/
protected ASTParser getParser() {
if (parser == null) {
parser = ASTParser.newParser(AST.JLS3);
parser.setProject(project);
}
parser.setResolveBindings(true);
parser.setBindingsRecovery(true);
return parser;
}
/**
* Called before starting the analysis, so that the manager has a chance to initialize
*/
public void init() {
}
/**
* Clears the whole cache
*/
@Override
public void finish() {
// need to discard or commit all acquired working copies
for (Entry<ITypeRoot, CompilationUnit> entry : iTypeRoot2CompilationUnit.entrySet()) {
ITypeRoot iTypeRoot = entry.getKey();
CompilationUnit compilationUnit = entry.getValue();
ICompilationUnit workingCopy = null;
IRewriteTarget rewriteTarget= null;
TextChange change=null;
try {
// need to apply the changes and commit the working copy
if (!iTypeRoot.isReadOnly()) {
try {
String source = iTypeRoot.getBuffer().getContents();
Document document = new Document(source);
// computation of the text edits
TextEdit edits = compilationUnit.rewrite(document, project.getOptions(true));
workingCopy = iTypeRoot.getWorkingCopy(JavaMappingInterpreter.primaryWorkingCopyOwner, progressMonitor);
IWorkbenchWindow activeWorkbenchWindow = PlatformUI.getWorkbench().getActiveWorkbenchWindow();
//new code to accomodate the recommender system so the entire file doesn't get refreshed
if (activeWorkbenchWindow!=null && activeWorkbenchWindow.getActivePage().getActiveEditor()!=null){
IEditorPart activeEditor = activeWorkbenchWindow.getActivePage().getActiveEditor();
CompilationUnitChange cuChange = new CompilationUnitChange("FSMLEdit", workingCopy);
cuChange.setSaveMode(TextFileChange.LEAVE_DIRTY);
change= cuChange;
change.setEdit(edits);
rewriteTarget= (IRewriteTarget) activeEditor.getAdapter(IRewriteTarget.class);
if (rewriteTarget != null) {
rewriteTarget.beginCompoundChange();
}
change.initializeValidationData(new NullProgressMonitor());
RefactoringStatus valid;
try {
valid = change.isValid(new NullProgressMonitor());
if (valid.hasFatalError()) {
IStatus status= new Status(IStatus.ERROR, JavaPlugin.getPluginId(), IStatus.ERROR,
valid.getMessageMatchingSeverity(RefactoringStatus.FATAL), null);
throw new CoreException(status);
} else {
IUndoManager manager= RefactoringCore.getUndoManager();
manager.aboutToPerformChange(change);
Change undoChange= change.perform(new NullProgressMonitor());
manager.changePerformed(change, true);
if (undoChange != null) {
undoChange.initializeValidationData(new NullProgressMonitor());
manager.addUndo("FSMLEdit", undoChange);
}
}
} catch (OperationCanceledException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (CoreException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally{
}
if (CodeTransforms.currentProposal!=null){
TrackedNodePosition nodePosition=null;
if (edits instanceof MultiTextEdit){
TextEdit[] children = edits.getChildren();
int count = 0;
for (int i=0;i<children.length;i++){
if (!(children[i] instanceof InsertEdit)){
continue;
} else {
if (((InsertEdit)children[i]).getText().trim().replaceAll("\n|\t","").length()==0){
continue;
}
}
TextEdit.getCoverage(new TextEdit[]{children[i]});
TextEditGroup textEditGroup = new TextEditGroup("FSMLEditGroup",children[i]);
nodePosition = new TrackedNodePosition(textEditGroup,null);
if (count==0){
CodeTransforms.currentProposal.addLinkedPosition(nodePosition, true, "FSMLProposalLinkedPosition"+count);
} else {
CodeTransforms.currentProposal.addLinkedPosition(nodePosition, false, "FSMLProposalLinkedPosition"+count);
}
count++;
}
} else {
TextEditGroup textEditGroup = new TextEditGroup("FSMLEditGroup",edits);
nodePosition = new TrackedNodePosition(textEditGroup,null);
CodeTransforms.currentProposal.addLinkedPosition(nodePosition, true, "FSMLProposalLinkedPosition");
}
CodeTransforms.currentProposal=null;
}
} else {
// computation of the new source code
edits.apply(document);
String newSource = document.get();
// update of the compilation unit
workingCopy.getBuffer().setContents(newSource);
workingCopy.commitWorkingCopy(true, null);
}
} catch (MalformedTreeException e) {
// still need to discard
e.printStackTrace();
} catch (BadLocationException e) {
// still need to discard
e.printStackTrace();
} catch (IllegalStateException e) {
// thrown by rewrite is recording of modifications was not turned on. Can safely discard
} catch (JavaModelException e) {
e.printStackTrace();
}
}
} finally {
try {
if (workingCopy != null) {
workingCopy.discardWorkingCopy();
//entry.setValue(workingCopy.reconcile(AST.JLS3, false, null, progressMonitor));
}
} catch (JavaModelException e1) {
e1.printStackTrace();
}
if (rewriteTarget != null) {
rewriteTarget.endCompoundChange();
}
if (change != null) {
change.dispose();
}
}
}
/*cache.clear();
acquiredWorkingCopies.clear();*/
}
/**
* Gets the compilation unit for this java element. If not in cache, this method will parse
* the AST and put it in the cache.
*
* @param iJavaElement the java element
* @return the compilation unit
*/
@Override
public CompilationUnit getCompilationUnit(IJavaElement iJavaElement) {
ITypeRoot iTypeRoot = JavaModelUtils.getTypeRoot(iJavaElement);
return iTypeRoot != null ? getCompilationUnit(iTypeRoot) : null;
}
/**
* Gets a compilation unit that was parsed for this type root (a class file or a compilation unit
* from java model). If it was not parsed, it will parse.
*
* @param iTypeRoot the key (a type root)
* @return the compilation unit, or null
*/
@Override
public CompilationUnit getCompilationUnit(ITypeRoot iTypeRoot) {
try {
if (! iTypeRoot2CompilationUnit.containsKey(iTypeRoot)) {
parseTypeRoot(iTypeRoot);
}
return iTypeRoot2CompilationUnit.get(iTypeRoot);
} catch (JavaModelException e) {
e.printStackTrace();
}
return null;
}
/**
* Parses the element (or re-parses if it already was parsed) and returns the compilation unit
* resulting from it. This method will cache the results.
*
* @param iTypeRoot the java element to be parsed
* @return the compilation unit
* @throws JavaModelException
*/
@Override
public CompilationUnit parseTypeRoot(ITypeRoot iTypeRoot) throws JavaModelException {
if (iTypeRoot == null) return null;
if (!iTypeRoot.isStructureKnown() || iTypeRoot.getSource() == null) {
Stats.INSTANCE.logError("parseTypeRoot: there is no source for binary file " + iTypeRoot.getElementName());
return null;
}
getParser().setSource(iTypeRoot);
CompilationUnit compilationUnit = (CompilationUnit) parser.createAST(progressMonitor);
iTypeRoot2CompilationUnit.put(iTypeRoot, compilationUnit);
boolean titleShown = false;
// log "unresolved name" problems
for(IProblem p : compilationUnit.getProblems()) {
if ((p.getID() & IProblem.IgnoreCategoriesMask) == 50 ||
(p.getID() & IProblem.IgnoreCategoriesMask) == 324) {
if (! titleShown) {
titleShown = true;
Stats.INSTANCE.printMessage("Compilation Error(s) for : " + iTypeRoot.getElementName() + " might cause resolution problems:");
}
Stats.INSTANCE.printMessage(" " + p);
}
}
return compilationUnit;
}
/**
* Parses the type root referent to the element (or re-parses if it already was parsed) and
* returns the compilation unit resulting from it. This method will cache the results.
*
* @param element the java element to be parsed
* @return the compilation unit
* @throws JavaModelException
*/
@Override
public CompilationUnit parseTypeRoot(IJavaElement element) throws JavaModelException {
return parseTypeRoot(JavaModelUtils.getTypeRoot(element));
}
/**
* Caches the compilation unit for this type root.
*
* @param root the type root
* @param unit the compilation unit
*/
@Override
public void setCompilationUnit(ITypeRoot root, CompilationUnit unit) {
iTypeRoot2CompilationUnit.put(root, unit);
}
}