/******************************************************************************* * Copyright (c) 2012 VMware, Inc. * 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: * VMware, Inc. - initial API and implementation *******************************************************************************/ package org.springframework.ide.eclipse.quickfix.jdt; import java.util.ArrayList; import java.util.List; import java.util.Set; import org.eclipse.core.resources.IFile; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.ILocalVariable; import org.eclipse.jdt.core.ISourceRange; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.Annotation; import org.eclipse.jdt.core.dom.BodyDeclaration; import org.eclipse.jdt.core.dom.Expression; import org.eclipse.jdt.core.dom.MemberValuePair; import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.NormalAnnotation; import org.eclipse.jdt.core.dom.SimpleName; import org.eclipse.jdt.core.dom.SingleMemberAnnotation; import org.eclipse.jdt.core.dom.SingleVariableDeclaration; import org.eclipse.jdt.core.dom.StringLiteral; import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.jdt.internal.ui.text.correction.ASTResolving; import org.eclipse.jdt.internal.ui.text.correction.AssistContext; import org.eclipse.ltk.core.refactoring.Change; import org.eclipse.ltk.core.refactoring.RefactoringStatus; import org.eclipse.ltk.core.refactoring.TextChange; import org.eclipse.ltk.core.refactoring.TextFileChange; import org.eclipse.ltk.core.refactoring.participants.CheckConditionsContext; import org.eclipse.ltk.core.refactoring.participants.ProcessorBasedRefactoring; import org.eclipse.ltk.core.refactoring.participants.RenameArguments; import org.eclipse.ltk.core.refactoring.participants.RenameParticipant; import org.eclipse.text.edits.MultiTextEdit; import org.eclipse.text.edits.ReplaceEdit; import org.eclipse.text.edits.TextEdit; import org.springframework.ide.eclipse.quickfix.jdt.util.ProposalCalculatorUtil; import org.springsource.ide.eclipse.commons.core.StatusHandler; /** * Rename participant for renaming template URI variable and path variable when * a method parameter is renamed * * @author Terry Denney */ public class RequestMappingRenameParticipant extends RenameParticipant { private IFile file; private String oldName; private StringLiteral pathVariableLiteral; private PathVariableSourceRange methodPathVariableSourceRange; private PathVariableSourceRange typePathVariableSourceRange; private class PathVariableSourceRange { private final List<Integer> offsets; public PathVariableSourceRange() { offsets = new ArrayList<Integer>(); } public void addOffset(int offset) { offsets.add(offset); } public List<Integer> getOffsets() { return offsets; } } @Override public RefactoringStatus checkConditions(IProgressMonitor pm, CheckConditionsContext context) throws OperationCanceledException { return new RefactoringStatus(); } private void getPositions(TextEdit edit, List<Integer> positions) { if (edit instanceof MultiTextEdit) { for (TextEdit child : ((MultiTextEdit) edit).getChildren()) { getPositions(child, positions); } } else { positions.add(edit.getOffset()); } } @Override public Change createChange(IProgressMonitor pm) throws CoreException, OperationCanceledException { RenameArguments arguments = getArguments(); String newName = arguments.getNewName(); MultiTextEdit multiEdit = new MultiTextEdit(); List<Integer> offsets = new ArrayList<Integer>(); offsets.addAll(typePathVariableSourceRange.getOffsets()); offsets.addAll(methodPathVariableSourceRange.getOffsets()); if (pathVariableLiteral != null) { offsets.add(pathVariableLiteral.getStartPosition() + 1); } // need to look at already refactored positions to make sure offsets are // correct List<Integer> refactoredPositions = new ArrayList<Integer>(); ProcessorBasedRefactoring refactoring = getProcessor().getRefactoring(); if (refactoring != null) { TextChange textChange = refactoring.getTextChange(file); if (textChange != null) { TextEdit edit = textChange.getEdit(); getPositions(edit, refactoredPositions); } } int changeCount = 0; int newLength = newName.length(); int oldLength = oldName.length(); int diff = newLength - oldLength; for (Integer offset : offsets) { List<Integer> toBeRemoved = new ArrayList<Integer>(); for (int position : refactoredPositions) { if (position < offset) { changeCount++; // once a refactoredPosition is accounted for, remove from // list so that it won't be double counted toBeRemoved.add(position); } } refactoredPositions.removeAll(toBeRemoved); multiEdit.addChild(new ReplaceEdit(offset + changeCount * diff, oldLength, newName)); } if (multiEdit.getChildrenSize() > 0) { TextFileChange change = new TextFileChange("", file); change.setEdit(multiEdit); return change; } else { return null; } } private ICompilationUnit getCompilationUnit(IJavaElement element) { if (element == null || element instanceof ICompilationUnit) { return (ICompilationUnit) element; } return getCompilationUnit(element.getParent()); } @Override public String getName() { return "Rename path variable referenced in @Controller class"; } @SuppressWarnings("unchecked") private void addPathVariableSourceRanges(BodyDeclaration decl, String pathVariable, PathVariableSourceRange sourceRange) throws JavaModelException { if (decl == null) { return; } Set<Annotation> annotations = ProposalCalculatorUtil.findAnnotations("RequestMapping", decl); for (Annotation annotation : annotations) { Expression value = null; if (annotation instanceof SingleMemberAnnotation) { value = ((SingleMemberAnnotation) annotation).getValue(); } else if (annotation instanceof NormalAnnotation) { List<MemberValuePair> pairs = ((NormalAnnotation) annotation).values(); for (MemberValuePair pair : pairs) { if ("value".equals(pair.getName().getFullyQualifiedName())) { value = pair.getValue(); break; } } } if (value instanceof StringLiteral) { String uri = ((StringLiteral) value).getLiteralValue(); int offset = value.getStartPosition() + 1; // skip opening quote while (uri != null && uri.length() > 0) { int index = uri.indexOf("{" + pathVariable + "}"); if (index > 0) { int pathVariableLength = pathVariable.length(); sourceRange.addOffset(offset + index + 1); offset += index + pathVariableLength + 2; uri = uri.substring(index + pathVariableLength + 2); } else { uri = null; } } } } } @Override protected boolean initialize(Object element) { boolean hasPathVariable = false; if (element instanceof ILocalVariable) { ILocalVariable variable = (ILocalVariable) element; ICompilationUnit compilationUnit = getCompilationUnit(variable); file = (IFile) compilationUnit.getResource(); try { ISourceRange sourceRange = variable.getSourceRange(); AssistContext assistContext = new AssistContext(compilationUnit, null, sourceRange.getOffset(), sourceRange.getLength()); ASTNode node = assistContext.getCoveringNode(); if (node instanceof SingleVariableDeclaration) { SingleVariableDeclaration varDecl = (SingleVariableDeclaration) node; Set<Annotation> annotations = ProposalCalculatorUtil.findAnnotations("PathVariable", varDecl); SimpleName varDeclName = varDecl.getName(); String varName = varDeclName.getFullyQualifiedName(); String pathVariableName = varName; StringLiteral pathVariableLiteral = null; for (Annotation annotation : annotations) { if (annotation instanceof SingleMemberAnnotation) { Expression value = ((SingleMemberAnnotation) annotation).getValue(); if (value instanceof StringLiteral) { pathVariableLiteral = ((StringLiteral) value); pathVariableName = pathVariableLiteral.getLiteralValue(); break; } } } if (varName.equals(pathVariableName)) { hasPathVariable = true; this.oldName = varName; this.pathVariableLiteral = pathVariableLiteral; // find reference in method annotation MethodDeclaration methodDecl = (MethodDeclaration) ASTResolving.findAncestor(varDecl, ASTNode.METHOD_DECLARATION); methodPathVariableSourceRange = new PathVariableSourceRange(); addPathVariableSourceRanges(methodDecl, pathVariableName, methodPathVariableSourceRange); // find reference in type annotation TypeDeclaration typeDecl = (TypeDeclaration) ASTResolving.findAncestor(varDecl, ASTNode.TYPE_DECLARATION); typePathVariableSourceRange = new PathVariableSourceRange(); addPathVariableSourceRanges(typeDecl, pathVariableName, typePathVariableSourceRange); } } } catch (JavaModelException e) { StatusHandler.log(e.getStatus()); } } return hasPathVariable; } }