Javafx: Rename nested controller field along with the corresponding fx:id attribute or field (IDEA-153810)

This commit is contained in:
Pavel Dolgov
2016-06-17 19:39:10 +03:00
parent 461cd5a7bc
commit 49fd96c74a
16 changed files with 250 additions and 7 deletions
@@ -190,6 +190,19 @@ public class JavaFXRenameTest extends AbstractJavaFXRenameTest {
assertMethodExists(psiClass, "getNewName");
}
public void testNestedControllerIdFromFxml() throws Exception {
doTestHandler("newName", getTestName(false) + "Internal");
myFixture.checkResultByFile(getTestName(false) + ".java", getTestName(false) + "_after.java", false);
}
public void testNestedControllerIdFromJava() throws Exception {
myFixture.configureByFiles(getTestName(false) + ".java", getTestName(false) + "Internal.java", getTestName(true) + ".fxml");
final PsiElement elementAtCaret = myFixture.getElementAtCaret();
new RenameProcessor(getProject(), elementAtCaret, "newName", false, false).run();
myFixture.checkResultByFile(getTestName(true) + ".fxml", getTestName(true) + "_after.fxml", false);
myFixture.checkResultByFile(getTestName(false) + ".java", getTestName(false) + "_after.java", false);
}
public void testControllerStringProperty() throws Exception {
doTestProperty("newName", false);
}
@@ -15,6 +15,7 @@
<codeInsight.lineMarkerProvider language="JAVA" implementationClass="org.jetbrains.plugins.javaFX.fxml.codeInsight.JavaFxRelatedItemLineMarkerProvider"/>
<codeInsight.lineMarkerProvider language="Groovy" implementationClass="org.jetbrains.plugins.javaFX.fxml.codeInsight.JavaFxRelatedItemLineMarkerProvider"/>
<useScopeEnlarger implementation="org.jetbrains.plugins.javaFX.fxml.refs.JavaFxScopeEnlarger"/>
<renamePsiElementProcessor implementation="org.jetbrains.plugins.javaFX.refactoring.JavaFxRenameFxIdFieldProcessor" order="first"/>
<localInspection language="XML" shortName="JavaFxDefaultTag" displayName="Unnecessary default tag" groupName="JavaFX"
enabledByDefault="true" level="WARNING" implementationClass="org.jetbrains.plugins.javaFX.fxml.codeInsight.inspections.JavaFxDefaultTagInspection"/>
@@ -6,14 +6,14 @@ import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiReference;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.resolve.reference.impl.PsiMultiReference;
import com.intellij.psi.util.PropertyUtil;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.xml.XmlAttributeValue;
import com.intellij.psi.xml.XmlFile;
import com.intellij.psi.xml.XmlTag;
import com.intellij.refactoring.RenameRefactoring;
import com.intellij.refactoring.openapi.impl.JavaRenameRefactoringImpl;
import com.intellij.refactoring.rename.PsiElementRenameHandler;
@@ -25,8 +25,10 @@ import gnu.trove.THashMap;
import gnu.trove.THashSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.javaFX.fxml.FxmlConstants;
import org.jetbrains.plugins.javaFX.fxml.JavaFxCommonNames;
import org.jetbrains.plugins.javaFX.fxml.JavaFxFileTypeFactory;
import org.jetbrains.plugins.javaFX.fxml.JavaFxPsiUtil;
import org.jetbrains.plugins.javaFX.fxml.refs.JavaFxComponentIdReferenceProvider;
import org.jetbrains.plugins.javaFX.fxml.refs.JavaFxFieldIdReferenceProvider;
import org.jetbrains.plugins.javaFX.fxml.refs.JavaFxPropertyReference;
@@ -87,23 +89,26 @@ public class JavaFxPropertyRenameHandler implements RenameHandler {
}
}
if (reference instanceof JavaFxFieldIdReferenceProvider.JavaFxControllerFieldRef) {
final XmlAttributeValue fxIdValueElement =
((JavaFxFieldIdReferenceProvider.JavaFxControllerFieldRef)reference).getXmlAttributeValue();
final Set<PsiElement> elementsToRename = new THashSet<>();
JavaFxRenameAttributeProcessor.visitReferencedElements(references, psiElement -> {
if (psiElement != null) {
elementsToRename.add(psiElement);
}
});
ContainerUtil.addIfNotNull(elementsToRename, getNestedControllerField(fxIdValueElement));
if (!canRename(project, editor, elementsToRename)) {
return;
}
final XmlAttributeValue fxIdElement = ((JavaFxFieldIdReferenceProvider.JavaFxControllerFieldRef)reference).getXmlAttributeValue();
if (ApplicationManager.getApplication().isUnitTestMode()) {
final String newName = PsiElementRenameHandler.DEFAULT_NAME.getData(dataContext);
assert newName != null : "Rename property";
new RenameDialog(project, fxIdElement, null, editor).performRename(newName);
doRenameFxId(fxIdValueElement, newName, false, false);
}
else {
new RenameDialog(project, fxIdElement, null, editor).show();
new RenameFxIdDialog(fxIdValueElement, editor).show();
}
}
}
@@ -180,6 +185,33 @@ public class JavaFxPropertyRenameHandler implements RenameHandler {
if (key != null) map.put(key, value);
}
public static void doRenameFxId(XmlAttributeValue fxIdValueElement, String newName, boolean searchInComments, boolean previewUsages) {
final RenameRefactoring rename =
new JavaRenameRefactoringImpl(fxIdValueElement.getProject(), fxIdValueElement, newName, searchInComments, false);
rename.setPreviewUsages(previewUsages);
final PsiField nestedControllerField = getNestedControllerField(fxIdValueElement);
if (nestedControllerField != null) {
rename.addElement(nestedControllerField, newName + FxmlConstants.CONTROLLER_SUFFIX);
}
rename.run();
}
@Nullable
private static PsiField getNestedControllerField(@NotNull XmlAttributeValue fxIdValueElement) {
final String fxId = fxIdValueElement.getValue();
if (!StringUtil.isEmpty(fxId)) {
final XmlTag tag = PsiTreeUtil.getParentOfType(fxIdValueElement, XmlTag.class);
if (tag != null && FxmlConstants.FX_INCLUDE.equals(tag.getName())) {
final PsiClass controllerClass = JavaFxPsiUtil.getControllerClass(tag.getContainingFile());
if (controllerClass != null) {
return controllerClass.findFieldByName(fxId + FxmlConstants.CONTROLLER_SUFFIX, true);
}
}
}
return null;
}
private static class PropertyRenameDialog extends RenameDialog {
private final JavaFxPropertyReference myPropertyReference;
@@ -199,4 +231,19 @@ public class JavaFxPropertyRenameHandler implements RenameHandler {
close(DialogWrapper.OK_EXIT_CODE);
}
}
private static class RenameFxIdDialog extends RenameDialog {
public RenameFxIdDialog(@NotNull XmlAttributeValue fxIdValueElement, Editor editor) {
super(fxIdValueElement.getProject(), fxIdValueElement, null, editor);
}
@Override
protected void doAction() {
final String newName = getNewName();
final PsiElement element = getPsiElement();
assert element instanceof XmlAttributeValue;
doRenameFxId(((XmlAttributeValue)element), newName, isSearchInComments(), isPreviewUsages());
close(DialogWrapper.OK_EXIT_CODE);
}
}
}
@@ -0,0 +1,122 @@
/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.plugins.javaFX.refactoring;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.search.SearchScope;
import com.intellij.psi.xml.XmlTag;
import com.intellij.refactoring.rename.RenamePsiElementProcessor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.javaFX.fxml.FxmlConstants;
import org.jetbrains.plugins.javaFX.indexing.JavaFxControllerClassIndex;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
/**
* @author Pavel.Dolgov
*/
public class JavaFxRenameFxIdFieldProcessor extends RenamePsiElementProcessor {
@Override
public boolean canProcessElement(@NotNull PsiElement element) {
final NestedControllerCandidate nestedControllerCandidate = findNestedControllerCandidate(element);
final Collection<PsiFile> fxmls = findFxmlWithController(nestedControllerCandidate);
return !fxmls.isEmpty();
}
@Override
public boolean isInplaceRenameSupported() {
return false;
}
@Override
public void prepareRenaming(PsiElement element, String newName, Map<PsiElement, String> allRenames, SearchScope scope) {
final NestedControllerCandidate nestedControllerCandidate = findNestedControllerCandidate(element);
if (nestedControllerCandidate != null) {
final Collection<PsiFile> fxmls = findFxmlWithController(nestedControllerCandidate);
for (PsiFile fxml : fxmls) {
final Ref<Boolean> found = new Ref<>(false);
fxml.accept(new XmlRecursiveElementVisitor() {
@Override
public void visitXmlTag(XmlTag tag) {
super.visitXmlTag(tag);
if (found.get()) return;
if (FxmlConstants.FX_INCLUDE.equals(tag.getName())) {
final String value = tag.getAttributeValue(FxmlConstants.FX_ID);
if (StringUtil.equals(nestedControllerCandidate.fxId, value)) {
found.set(true);
}
}
}
});
if (found.get()) {
allRenames.put(nestedControllerCandidate.nestedControllerField, newName + FxmlConstants.CONTROLLER_SUFFIX);
return;
}
}
}
}
@Nullable
private static NestedControllerCandidate findNestedControllerCandidate(@NotNull PsiElement element) {
if (element instanceof PsiField) {
final PsiField field = (PsiField)element;
final String fxId = field.getName();
if (!StringUtil.isEmpty(fxId)) {
final PsiClass containingClass = field.getContainingClass();
if (containingClass != null) {
final PsiField nestedControllerField = containingClass.findFieldByName(fxId + FxmlConstants.CONTROLLER_SUFFIX, true);
if (nestedControllerField != null) {
final PsiType psiType = nestedControllerField.getType();
if (!(psiType instanceof PsiPrimitiveType) && !(psiType instanceof PsiArrayType)) { // optimization
return new NestedControllerCandidate(fxId, nestedControllerField, containingClass);
}
}
}
}
}
return null;
}
@NotNull
private static Collection<PsiFile> findFxmlWithController(@Nullable NestedControllerCandidate nestedControllerCandidate) {
if (nestedControllerCandidate != null) {
final String qualifiedName = nestedControllerCandidate.controllerClass.getQualifiedName();
if (qualifiedName != null) {
final Project project = nestedControllerCandidate.controllerClass.getProject();
return JavaFxControllerClassIndex.findFxmlWithController(project, qualifiedName);
}
}
return Collections.emptyList();
}
private static class NestedControllerCandidate {
private final String fxId;
private final PsiField nestedControllerField;
private final PsiClass controllerClass;
private NestedControllerCandidate(@NotNull String fxId, @NotNull PsiField nestedControllerField, @NotNull PsiClass controllerClass) {
this.fxId = fxId;
this.nestedControllerField = nestedControllerField;
this.controllerClass = controllerClass;
}
}
}
@@ -0,0 +1,5 @@
import javafx.fxml.FXML;
class NestedControllerIdFromFxml {
@FXML NestedControllerIdFromFxmlInternal internalController;
}
@@ -0,0 +1,2 @@
class NestedControllerIdFromFxmlInternal {
}
@@ -0,0 +1,5 @@
import javafx.fxml.FXML;
class NestedControllerIdFromFxml {
@FXML NestedControllerIdFromFxmlInternal newNameController;
}
@@ -0,0 +1,8 @@
import javafx.fxml.FXML;
import javafx.scene.layout.VBox;
class NestedControllerIdFromJava {
@FXML
private VBox int<caret>ernal;
@FXML NestedControllerIdFromJavaInternal internalController;
}
@@ -0,0 +1,2 @@
class NestedControllerIdFromJavaInternal {
}
@@ -0,0 +1,8 @@
import javafx.fxml.FXML;
import javafx.scene.layout.VBox;
class NestedControllerIdFromJava {
@FXML
private VBox newName;
@FXML NestedControllerIdFromJavaInternal newNameController;
}
@@ -0,0 +1,5 @@
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.Button?>
<VBox xmlns:fx="http://javafx.com/fxml" fx:controller="NestedControllerIdFromFxml">
<fx:include fx:id="int<caret>ernal" source="nestedControllerIdFromFxmlInternal.fxml"/>
</VBox>
@@ -0,0 +1,5 @@
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.Button?>
<VBox xmlns:fx="http://javafx.com/fxml" fx:controller="NestedControllerIdFromFxmlInternal">
<Button/>
</VBox>
@@ -0,0 +1,5 @@
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.Button?>
<VBox xmlns:fx="http://javafx.com/fxml" fx:controller="NestedControllerIdFromFxml">
<fx:include fx:id="newName" source="nestedControllerIdFromFxmlInternal.fxml"/>
</VBox>
@@ -0,0 +1,5 @@
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.Button?>
<VBox xmlns:fx="http://javafx.com/fxml" fx:controller="NestedControllerIdFromJava">
<fx:include fx:id="internal" source="nestedControllerIdFromJavaInternal.fxml"/>
</VBox>
@@ -0,0 +1,5 @@
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.Button?>
<VBox xmlns:fx="http://javafx.com/fxml" fx:controller="NestedControllerIdFromJavaInternal">
<Button/>
</VBox>
@@ -0,0 +1,5 @@
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.control.Button?>
<VBox xmlns:fx="http://javafx.com/fxml" fx:controller="NestedControllerIdFromJava">
<fx:include fx:id="newName" source="nestedControllerIdFromJavaInternal.fxml"/>
</VBox>