package mit.edu.concurrencyrefactorings.refactorings; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.eclipse.core.resources.IFile; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.core.runtime.SubProgressMonitor; import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.IField; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.core.ISourceRange; import org.eclipse.jdt.core.IType; 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.ASTNode; import org.eclipse.jdt.core.dom.AbstractTypeDeclaration; import org.eclipse.jdt.core.dom.AnonymousClassDeclaration; import org.eclipse.jdt.core.dom.ChildListPropertyDescriptor; import org.eclipse.jdt.core.dom.ClassInstanceCreation; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.Expression; import org.eclipse.jdt.core.dom.FieldDeclaration; import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.core.dom.IVariableBinding; import org.eclipse.jdt.core.dom.Message; import org.eclipse.jdt.core.dom.ParameterizedType; import org.eclipse.jdt.core.dom.QualifiedType; import org.eclipse.jdt.core.dom.SimpleName; import org.eclipse.jdt.core.dom.SimpleType; import org.eclipse.jdt.core.dom.Type; import org.eclipse.jdt.core.dom.VariableDeclarationFragment; import org.eclipse.jdt.core.dom.rewrite.ASTRewrite; import org.eclipse.jdt.core.dom.rewrite.ImportRewrite; import org.eclipse.jdt.core.refactoring.IJavaRefactorings; import org.eclipse.jdt.core.refactoring.descriptors.JavaRefactoringDescriptor; import org.eclipse.jdt.core.search.IJavaSearchConstants; import org.eclipse.jdt.core.search.SearchPattern; import org.eclipse.jdt.internal.corext.codemanipulation.StubUtility; import org.eclipse.jdt.internal.corext.dom.ASTNodeFactory; import org.eclipse.jdt.internal.corext.dom.ASTNodes; import org.eclipse.jdt.internal.corext.dom.ModifierRewrite; import org.eclipse.jdt.internal.corext.dom.NodeFinder; import org.eclipse.jdt.internal.corext.refactoring.Checks; import org.eclipse.jdt.internal.corext.refactoring.RefactoringCoreMessages; import org.eclipse.jdt.internal.corext.refactoring.RefactoringScopeFactory; import org.eclipse.jdt.internal.corext.refactoring.RefactoringSearchEngine; import org.eclipse.jdt.internal.corext.refactoring.base.JavaStatusContext; import org.eclipse.jdt.internal.corext.refactoring.changes.DynamicValidationRefactoringChange; import org.eclipse.jdt.internal.corext.refactoring.changes.TextChangeCompatibility; import org.eclipse.jdt.internal.corext.refactoring.util.RefactoringASTParser; import org.eclipse.jdt.internal.corext.refactoring.util.ResourceUtil; import org.eclipse.jdt.internal.corext.refactoring.util.TextChangeManager; import org.eclipse.jdt.internal.corext.util.Messages; import org.eclipse.jdt.internal.ui.JavaPlugin; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.Document; import org.eclipse.jface.text.IDocument; import org.eclipse.ltk.core.refactoring.Change; import org.eclipse.ltk.core.refactoring.Refactoring; import org.eclipse.ltk.core.refactoring.RefactoringDescriptor; import org.eclipse.ltk.core.refactoring.RefactoringStatus; import org.eclipse.ltk.core.refactoring.TextChange; import org.eclipse.ltk.core.refactoring.participants.ResourceChangeChecker; import org.eclipse.text.edits.DeleteEdit; import org.eclipse.text.edits.InsertEdit; import org.eclipse.text.edits.MalformedTreeException; import org.eclipse.text.edits.MoveSourceEdit; import org.eclipse.text.edits.MultiTextEdit; import org.eclipse.text.edits.TextEdit; import org.eclipse.text.edits.TextEditGroup; public class ConvertToConcurrentHashMapRefactoring extends Refactoring { HashMap hm = new HashMap(); private static final String NO_NAME = ""; private IField fField; private CompilationUnit fRoot; private VariableDeclarationFragment fFieldDeclarationFragment; private ASTRewrite fRewriter; private TextChangeManager fChangeManager; private ImportRewrite fImportRewrite; private static final String LINKED_NAME= "name"; //$NON-NLS-1$ private TextEditGroup extractMethodTextEdit; private TextEditGroup removeSetMethodInvocationTextEdit; private boolean usingCHMOnlyMethods = false; public ConvertToConcurrentHashMapRefactoring(IField field){ fChangeManager= new TextChangeManager(); fField= field; } @Override public RefactoringStatus checkFinalConditions(IProgressMonitor pm) throws CoreException, OperationCanceledException { RefactoringStatus result= new RefactoringStatus(); fChangeManager.clear(); pm.beginTask("", 12); pm.setTaskName("Convert to ConcurrentHashMap: checking preconditions"); pm.worked(1); if (result.hasFatalError()) return result; pm.setTaskName("ConvertToConcurrentHashMap: searching for cunits"); final SubProgressMonitor subPm= new SubProgressMonitor(pm, 5); ICompilationUnit[] affectedCUs= RefactoringSearchEngine.findAffectedCompilationUnits( SearchPattern.createPattern(fField, IJavaSearchConstants.ALL_OCCURRENCES), RefactoringScopeFactory.create(fField, true), subPm, result, true); if (result.hasFatalError()) return result; pm.setTaskName("Field_analyzing"); IProgressMonitor sub= new SubProgressMonitor(pm, 5); sub.beginTask("", affectedCUs.length); IVariableBinding fieldIdentifier= fFieldDeclarationFragment.resolveBinding(); AbstractTypeDeclaration declaringClass= ((AbstractTypeDeclaration)ASTNodes.getParent(fFieldDeclarationFragment, AbstractTypeDeclaration.class)); List ownerDescriptions= new ArrayList(); ICompilationUnit owner= fField.getCompilationUnit(); fImportRewrite= StubUtility.createImportRewrite(fRoot, true); for (int i= 0; i < affectedCUs.length; i++) { ICompilationUnit unit= affectedCUs[i]; sub.subTask(unit.getElementName()); CompilationUnit root= null; ASTRewrite rewriter= null; ImportRewrite importRewrite; List descriptions; if (owner.equals(unit)) { root= fRoot; rewriter= fRewriter; importRewrite= fImportRewrite; descriptions= ownerDescriptions; } else { root= new RefactoringASTParser(AST.JLS3).parse(unit, true); rewriter= ASTRewrite.create(root.getAST()); descriptions= new ArrayList(); importRewrite= StubUtility.createImportRewrite(root, true); } checkCompileErrors(result, root, unit); AccessAnalyzerForConcurrentHashMap analyzer= new AccessAnalyzerForConcurrentHashMap(this, unit, fieldIdentifier, declaringClass, rewriter, importRewrite, root); root.accept(analyzer); result.merge(analyzer.getStatus()); if (result.hasFatalError()) { fChangeManager.clear(); return result; } descriptions.addAll(analyzer.getGroupDescriptions()); if (!owner.equals(unit)) createEdits(unit, rewriter, descriptions, importRewrite); sub.worked(1); if (pm.isCanceled()) throw new OperationCanceledException(); this.usingCHMOnlyMethods = analyzer.usingCHMOnlyMethods; } ownerDescriptions.addAll(addChangeDeclaringType(fRoot, fRewriter)); createEdits(owner, fRewriter, ownerDescriptions, fImportRewrite); if (extractMethodTextEdit != null) applyExtractMethodEdits(owner, extractMethodTextEdit); sub.done(); IFile[] filesToBeModified= ResourceUtil.getFiles(fChangeManager.getAllCompilationUnits()); result.merge(Checks.validateModifiesFiles(filesToBeModified, getValidationContext())); if (result.hasFatalError()) return result; ResourceChangeChecker.checkFilesToBeChanged(filesToBeModified, new SubProgressMonitor(pm, 1)); return result; } private List<String> test = new ArrayList<String>(); private Collection addChangeDeclaringType(CompilationUnit root, ASTRewrite rewriter) { AST ast = root.getAST(); VariableDeclarationFragment newVariableDeclarationFragment = ast.newVariableDeclarationFragment(); SimpleName newSimpleName = ast.newSimpleName(fField.getElementName()); newVariableDeclarationFragment.setName(newSimpleName); FieldDeclaration oldFieldDeclaration = (FieldDeclaration)ASTNodes.getParent(fFieldDeclarationFragment, FieldDeclaration.class); List fragments = oldFieldDeclaration.fragments(); Type oldTypeLS = oldFieldDeclaration.getType(); Type newTypeLS = (Type) ASTNode.copySubtree(ast, oldTypeLS); if(newTypeLS instanceof ParameterizedType) { newTypeLS = (Type) ASTNode.copySubtree(ast, ((ParameterizedType)newTypeLS).getType()); } if(newTypeLS instanceof SimpleType) { String fqName = ((SimpleType)newTypeLS).getName().getFullyQualifiedName(); if(fqName.equals("HashMap") || fqName.equals("java.util.HashMap")) { newTypeLS = ast.newSimpleType(ASTNodeFactory.newName(ast, "ConcurrentHashMap")); } else if(fqName.equals("Map") || fqName.equals("java.util.Map")) { if(usingCHMOnlyMethods) { newTypeLS = ast.newSimpleType(ASTNodeFactory.newName(ast, "ConcurrentHashMap")); } } } else { throw new IllegalArgumentException("Unexpected type for " + fField.getElementName()); } Expression initializer = fFieldDeclarationFragment.getInitializer(); if (initializer != null) { if(initializer instanceof ClassInstanceCreation) { boolean originalInitIsHashMap = false; Type type = ((ClassInstanceCreation)initializer).getType(); if(type instanceof ParameterizedType) { type = ((ParameterizedType)type).getType(); } if(type instanceof SimpleType) { if((((SimpleType)type).getName()).getFullyQualifiedName().equals("HashMap")) { originalInitIsHashMap = true; } } else if(type instanceof QualifiedType) { if((((QualifiedType)type).getName()).getFullyQualifiedName().equals("HashMap")) { originalInitIsHashMap = true; } } if(originalInitIsHashMap) { Type newTypeRS = ast.newSimpleType(ASTNodeFactory.newName(ast, "ConcurrentHashMap")); ClassInstanceCreation concurrentInstanceCreation = ast.newClassInstanceCreation(); Type initializerType = ((ClassInstanceCreation)initializer).getType(); if(initializerType instanceof ParameterizedType) { newTypeRS = ast.newParameterizedType(newTypeRS); Type oldTypeRS = ((ParameterizedType) initializerType).getType(); List<Type> oldTypeArgumentsRS = ((ParameterizedType) initializerType).typeArguments(); List<Type> newTypeArgumentsRS = ((ParameterizedType)newTypeRS).typeArguments(); newTypeArgumentsRS.addAll(ASTNode.copySubtrees(ast, oldTypeArgumentsRS)); } concurrentInstanceCreation.setType(newTypeRS); newVariableDeclarationFragment.setInitializer((Expression) concurrentInstanceCreation); } else { newVariableDeclarationFragment.setInitializer((Expression) ASTNode.copySubtree(ast, initializer)); } } else { newVariableDeclarationFragment.setInitializer((Expression) ASTNode.copySubtree(ast, initializer)); } } TextEditGroup gd = new TextEditGroup("Change Type"); if(oldTypeLS instanceof ParameterizedType) { newTypeLS = ast.newParameterizedType(newTypeLS); List<Type> oldTypeArgumentsLS = ((ParameterizedType)oldTypeLS).typeArguments(); List<Type> newTypeArgumentsLS = ((ParameterizedType)newTypeLS).typeArguments(); newTypeArgumentsLS.addAll(ASTNode.copySubtrees(ast,oldTypeArgumentsLS)); } FieldDeclaration newFieldDeclaration = ast.newFieldDeclaration(newVariableDeclarationFragment); newFieldDeclaration.setType(newTypeLS); ModifierRewrite.create(fRewriter, newFieldDeclaration).copyAllModifiers(oldFieldDeclaration, gd); if (fragments.size() > 1) { rewriter.remove(fFieldDeclarationFragment, gd); ChildListPropertyDescriptor descriptor= getBodyDeclarationsProperty(oldFieldDeclaration.getParent()); ModifierRewrite.create(rewriter, newFieldDeclaration).copyAllModifiers(oldFieldDeclaration, gd); fRewriter.getListRewrite(oldFieldDeclaration.getParent(), descriptor).insertAfter(newFieldDeclaration, oldFieldDeclaration, gd); } else { // case of only 1 declaration fragment fRewriter.replace(oldFieldDeclaration, newFieldDeclaration, gd); } ArrayList<TextEditGroup> group = new ArrayList<TextEditGroup>(); group.add(gd); return group; } private ChildListPropertyDescriptor getBodyDeclarationsProperty(ASTNode declaration) { if (declaration instanceof AnonymousClassDeclaration) return AnonymousClassDeclaration.BODY_DECLARATIONS_PROPERTY; else if (declaration instanceof AbstractTypeDeclaration) return ((AbstractTypeDeclaration) declaration).getBodyDeclarationsProperty(); Assert.isTrue(false); return null; } private void createEdits(ICompilationUnit unit, ASTRewrite rewriter, List groups, ImportRewrite importRewrite) throws CoreException { TextChange change= fChangeManager.get(unit); MultiTextEdit root= new MultiTextEdit(); change.setEdit(root); TextEdit importEdit = importRewrite.rewriteImports(null); TextChangeCompatibility.addTextEdit(fChangeManager.get(unit), "Update Imports", importEdit); root.addChild(rewriter.rewriteAST()); for (Iterator iter= groups.iterator(); iter.hasNext();) { change.addTextEditGroup((TextEditGroup)iter.next()); } } private void applyExtractMethodEdits (ICompilationUnit unit, TextEditGroup extractMethodTextEdit) { TextChange change= fChangeManager.get(unit); TextEdit[] textEdits = extractMethodTextEdit.getTextEdits(); MultiTextEdit muxEdit = (MultiTextEdit) textEdits[0].getParent().getParent(); TextEdit[] childrenMuxesExtractMethod = muxEdit.getChildren(); TextEdit[] textEditsForSubstituteMethodInvocation = removeSetMethodInvocationTextEdit.getTextEdits(); TextEdit textEditToRemove = null; // try { // IDocument scratchDocument = new Document(unit.getSource()); // muxEdit.apply(scratchDocument); // } catch (JavaModelException e) { // // TODO Auto-generated catch block // e.printStackTrace(); // } catch (MalformedTreeException e) { // // TODO Auto-generated catch block // e.printStackTrace(); // } catch (BadLocationException e) { // // TODO Auto-generated catch block // e.printStackTrace(); // } // for (TextEdit textEdit : textEditsForSubstituteMethodInvocation) { // if ((textEdit instanceof InsertEdit) || (textEdit instanceof DeleteEdit)) { // textEditToRemove = textEdit; // } // removeTextEdit(muxEdit, textEditToRemove); // } // change.addEdit(muxEdit); // change.addTextEditGroup(extractMethodTextEdit); // //TextChangeCompatibility.addTextEdit(fChangeManager.get(unit), "Extract create value method", extractMethodTextEdit); } private void removeTextEdit(MultiTextEdit muxEdit, TextEdit textEditToRemove) { if (! muxEdit.removeChild(textEditToRemove)) { TextEdit[] childrenEdits = muxEdit.getChildren(); for (TextEdit childEdit : childrenEdits) { if (childEdit instanceof MultiTextEdit) { MultiTextEdit childMuxEdit = (MultiTextEdit) childEdit; removeTextEdit(childMuxEdit, textEditToRemove); } } } else { // add back the child of a DeleteEdit if (textEditToRemove instanceof DeleteEdit) { DeleteEdit deleteEdit = (DeleteEdit) textEditToRemove; TextEdit[] childrenOfDeleteEdit = deleteEdit.getChildren(); for (TextEdit childOfDeleteEdit : childrenOfDeleteEdit) { TextEdit copyOfChildOfDeleteEdit = childOfDeleteEdit.copy(); if (childOfDeleteEdit instanceof MoveSourceEdit) { ((MoveSourceEdit) copyOfChildOfDeleteEdit).setTargetEdit(((MoveSourceEdit)childOfDeleteEdit).getTargetEdit()); } muxEdit.addChild(copyOfChildOfDeleteEdit); } } } } @Override public RefactoringStatus checkInitialConditions(IProgressMonitor pm) throws CoreException, OperationCanceledException { RefactoringStatus result= new RefactoringStatus(); result.merge(Checks.checkAvailability(fField)); if (result.hasFatalError()) return result; fRoot= new RefactoringASTParser(AST.JLS3).parse(fField.getCompilationUnit(), true, pm); ISourceRange sourceRange= fField.getNameRange(); ASTNode node= NodeFinder.perform(fRoot, sourceRange.getOffset(), sourceRange.getLength()); if (node == null) { return mappingErrorFound(result, node); } fFieldDeclarationFragment= (VariableDeclarationFragment)ASTNodes.getParent(node, VariableDeclarationFragment.class); if (fFieldDeclarationFragment == null) { return mappingErrorFound(result, node); } if (fFieldDeclarationFragment.resolveBinding() == null) { if (!processCompilerError(result, node)) result.addFatalError("type not resolveable"); return result; } fRewriter= ASTRewrite.create(fRoot.getAST()); return result; } private RefactoringStatus mappingErrorFound(RefactoringStatus result, ASTNode node) { if (node != null && (node.getFlags() & ASTNode.MALFORMED) != 0 && processCompilerError(result, node)) return result; result.addFatalError(getMappingErrorMessage()); return result; } private String getMappingErrorMessage() { return Messages.format( "Convert to ConcurrentHashMap: cannot analyze selected field", new String[] {fField.getElementName()}); } private boolean processCompilerError(RefactoringStatus result, ASTNode node) { Message[] messages= ASTNodes.getMessages(node, ASTNodes.INCLUDE_ALL_PARENTS); if (messages.length == 0) return false; result.addFatalError(Messages.format( "Compiler errors with the field to be refactored", new String[] { fField.getElementName(), messages[0].getMessage()})); return true; } @Override public Change createChange(IProgressMonitor pm) throws CoreException, OperationCanceledException { String project= null; IJavaProject javaProject= fField.getJavaProject(); if (javaProject != null) project= javaProject.getElementName(); int flags= JavaRefactoringDescriptor.JAR_MIGRATION | JavaRefactoringDescriptor.JAR_REFACTORING | RefactoringDescriptor.STRUCTURAL_CHANGE | RefactoringDescriptor.MULTI_CHANGE; final IType declaring= fField.getDeclaringType(); try { if (declaring.isAnonymous() || declaring.isLocal()) flags|= JavaRefactoringDescriptor.JAR_SOURCE_ATTACHMENT; } catch (JavaModelException exception) { JavaPlugin.log(exception); } //TODO need to properly initialize the arguments so that this refactoring becomes recordable final Map arguments= new HashMap(); String description = "Convert HashMap to ConcurrentHashMap"; String comment = "Convert HashMap to ConcurrentHashMap"; final JavaRefactoringDescriptor descriptor= new JavaRefactoringDescriptor(IJavaRefactorings.ENCAPSULATE_FIELD, project, description, comment, arguments, flags) {}; final DynamicValidationRefactoringChange result= new DynamicValidationRefactoringChange(descriptor, getName()); TextChange[] changes= fChangeManager.getAllChanges(); pm.beginTask(NO_NAME, changes.length); pm.setTaskName("ConvertToConcurrentHashMap: create changes"); for (int i= 0; i < changes.length; i++) { result.add(changes[i]); pm.worked(1); } pm.done(); return result; } @Override public String getName() { return "Convert to ConcurrentHashMap"; } public void setField(IField field) { this.fField = field; } public RefactoringStatus setFieldName(String text) { // TODO Auto-generated method stub return null; } public IField getField() { return fField; } public String getFieldName() { return fField.getElementName(); } private boolean isIgnorableProblem(IProblem problem) { if (problem.getID() == IProblem.NotVisibleField) return true; return false; } private void checkCompileErrors(RefactoringStatus result, CompilationUnit root, ICompilationUnit element) { IProblem[] messages= root.getProblems(); for (int i= 0; i < messages.length; i++) { IProblem problem= messages[i]; if (!isIgnorableProblem(problem)) { result.addError(Messages.format( "ConvertToConcurrentHashMap: compiler errors", element.getElementName()), JavaStatusContext.create(element)); return; } } } public TextEditGroup getExtractMethodTextEdit() { return extractMethodTextEdit; } public void setExtractMethodTextEdit(TextEditGroup extractMethodTextEdit) { this.extractMethodTextEdit = extractMethodTextEdit; } public void setRemoveSetMethodInvocationEdit( TextEditGroup removeSetMethodInvocationTextEdit) { this.removeSetMethodInvocationTextEdit = removeSetMethodInvocationTextEdit; } }