javafx: import statement references (IDEA-100015); rename fixed (IDEA-100009)

This commit is contained in:
Anna Kozlova
2013-01-29 21:06:07 +04:00
parent a7f171bd11
commit 97b143bb46
15 changed files with 233 additions and 74 deletions
@@ -18,14 +18,15 @@ package org.jetbrains.plugins.javaFX.fxml;
import com.intellij.lang.ASTNode;
import com.intellij.lang.xml.XMLLanguage;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFileFactory;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.PostprocessReformattingAspect;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.xml.*;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
/**
@@ -55,18 +56,66 @@ public class JavaFxPsiUtil {
final Collection<XmlProcessingInstruction>
instructions = new ArrayList<XmlProcessingInstruction>(PsiTreeUtil.findChildrenOfType(prolog, XmlProcessingInstruction.class));
for (Iterator<XmlProcessingInstruction> iterator = instructions.iterator(); iterator.hasNext(); ) {
final XmlProcessingInstruction instruction = iterator.next();
final ASTNode node = instruction.getNode();
ASTNode xmlNameNode = node.findChildByType(XmlTokenType.XML_NAME);
ASTNode importNode = node.findChildByType(XmlTokenType.XML_TAG_CHARACTERS);
if (xmlNameNode == null || !instructionName.equals(xmlNameNode.getText()) || importNode == null) {
iterator.remove();
} else {
definedImports.add(importNode.getText());
for (final XmlProcessingInstruction instruction : instructions) {
final String instructionTarget = getInstructionTarget(instructionName, instruction);
if (instructionTarget != null) {
definedImports.add(instructionTarget);
}
}
}
return definedImports;
}
@Nullable
public static String getInstructionTarget(String instructionName, XmlProcessingInstruction instruction) {
final ASTNode node = instruction.getNode();
ASTNode xmlNameNode = node.findChildByType(XmlTokenType.XML_NAME);
ASTNode importNode = node.findChildByType(XmlTokenType.XML_TAG_CHARACTERS);
if (!(xmlNameNode == null || !instructionName.equals(xmlNameNode.getText()) || importNode == null)) {
return importNode.getText();
}
return null;
}
public static PsiClass findPsiClass(String name, XmlTag tag) {
return findPsiClass(name, parseImports((XmlFile)tag.getContainingFile()), tag, tag.getProject());
}
private static PsiClass findPsiClass(String name, List<String> imports, XmlTag tag, Project project) {
PsiClass psiClass = null;
if (imports != null) {
JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project);
PsiFile file = tag.getContainingFile();
for (String anImport : imports) {
if (StringUtil.endsWith(anImport, "." + name)) {
psiClass = psiFacade.findClass(anImport, file.getResolveScope());
} else if (StringUtil.endsWith(anImport, ".*")) {
psiClass = psiFacade.findClass(StringUtil.trimEnd(anImport, "*") + name, file.getResolveScope());
}
if (psiClass != null) {
return psiClass;
}
}
}
return psiClass;
}
public static void insertImportWhenNeeded(XmlFile xmlFile,
String shortName,
String qualifiedName) {
if (shortName != null && findPsiClass(shortName, xmlFile.getRootTag()) == null) {
final XmlDocument document = xmlFile.getDocument();
if (document != null) {
final XmlProcessingInstruction processingInstruction = createSingleImportInstruction(qualifiedName, xmlFile.getProject());
final XmlProlog prolog = document.getProlog();
if (prolog != null) {
prolog.add(processingInstruction);
} else {
document.addBefore(processingInstruction, document.getRootTag());
}
PostprocessReformattingAspect.getInstance(xmlFile.getProject()).doPostponedFormatting(xmlFile.getViewProvider());
}
}
}
}
@@ -4,7 +4,6 @@ import com.intellij.codeInsight.AnnotationUtil;
import com.intellij.codeInsight.daemon.Validator;
import com.intellij.codeInsight.daemon.impl.analysis.GenericsHighlightUtil;
import com.intellij.codeInsight.daemon.impl.analysis.HighlightUtil;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
@@ -13,7 +12,6 @@ import com.intellij.psi.util.InheritanceUtil;
import com.intellij.psi.util.PropertyUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.xml.XmlAttribute;
import com.intellij.psi.xml.XmlFile;
import com.intellij.psi.xml.XmlTag;
import com.intellij.util.ArrayUtil;
import com.intellij.util.Function;
@@ -41,11 +39,7 @@ public class JavaFxClassBackedElementDescriptor implements XmlElementDescriptor,
private final String myName;
public JavaFxClassBackedElementDescriptor(String name, XmlTag tag) {
this(name, findPsiClass(name, tag));
}
public static PsiClass findPsiClass(String name, XmlTag tag) {
return findPsiClass(name, JavaFxPsiUtil.parseImports((XmlFile)tag.getContainingFile()), tag, tag.getProject());
this(name, JavaFxPsiUtil.findPsiClass(name, tag));
}
public JavaFxClassBackedElementDescriptor(String name, PsiClass aClass) {
@@ -53,26 +47,6 @@ public class JavaFxClassBackedElementDescriptor implements XmlElementDescriptor,
myPsiClass = aClass;
}
private static PsiClass findPsiClass(String name, List<String> imports, XmlTag tag, Project project) {
PsiClass psiClass = null;
if (imports != null) {
JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project);
PsiFile file = tag.getContainingFile();
for (String anImport : imports) {
if (StringUtil.endsWith(anImport, "." + name)) {
psiClass = psiFacade.findClass(anImport, file.getResolveScope());
} else if (StringUtil.endsWith(anImport, ".*")) {
psiClass = psiFacade.findClass(StringUtil.trimEnd(anImport, "*") + name, file.getResolveScope());
}
if (psiClass != null) {
return psiClass;
}
}
}
return psiClass;
}
@Override
public String getQualifiedName() {
return myPsiClass != null ? myPsiClass.getQualifiedName() : getName();
@@ -331,7 +305,7 @@ public class JavaFxClassBackedElementDescriptor implements XmlElementDescriptor,
public static PsiMethod findPropertySetter(String attributeName, XmlTag context) {
final String packageName = StringUtil.getPackageName(attributeName);
if (context != null && !StringUtil.isEmptyOrSpaces(packageName)) {
final PsiClass classWithStaticProperty = findPsiClass(packageName, context);
final PsiClass classWithStaticProperty = JavaFxPsiUtil.findPsiClass(packageName, context);
if (classWithStaticProperty != null) {
return findPropertySetter(attributeName, classWithStaticProperty);
}
@@ -15,14 +15,13 @@
*/
package org.jetbrains.plugins.javaFX.fxml.refs;
import com.intellij.patterns.PatternCondition;
import com.intellij.patterns.XmlAttributeValuePattern;
import com.intellij.patterns.XmlPatterns;
import com.intellij.patterns.*;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.resolve.reference.impl.providers.JavaClassReferenceProvider;
import com.intellij.psi.xml.XmlAttributeValue;
import com.intellij.psi.xml.*;
import com.intellij.util.ProcessingContext;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.javaFX.fxml.FxmlConstants;
import org.jetbrains.plugins.javaFX.fxml.JavaFxFileTypeFactory;
@@ -52,6 +51,17 @@ public class FxmlReferencesContributor extends PsiReferenceContributor {
registrar.registerReferenceProvider(XmlPatterns.xmlAttributeValue().withValue(string().startsWith("@")).and(attributeValueInFxml),
new JavaFxLocationReferenceProvider());
registrar.registerReferenceProvider(PlatformPatterns.psiElement(XmlTokenType.XML_TAG_CHARACTERS).inFile(inFxmlElementPattern()), new ImportReferenceProvider());
}
private static PsiFilePattern.Capture<PsiFile> inFxmlElementPattern() {
return new PsiFilePattern.Capture<PsiFile>(new InitialPatternCondition<PsiFile>(PsiFile.class) {
@Override
public boolean accepts(@Nullable Object o, ProcessingContext context) {
return o instanceof PsiFile && JavaFxFileTypeFactory.isFxml((PsiFile)o);
}
});
}
private static PatternCondition<XmlAttributeValue> inFxmlCondition() {
@@ -0,0 +1,49 @@
/*
* 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.fxml.refs;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiReference;
import com.intellij.psi.PsiReferenceProvider;
import com.intellij.psi.xml.XmlProcessingInstruction;
import com.intellij.util.ArrayUtil;
import com.intellij.util.ProcessingContext;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.javaFX.fxml.JavaFxPsiUtil;
/**
* User: anna
*/
class ImportReferenceProvider extends PsiReferenceProvider {
@NotNull
@Override
public PsiReference[] getReferencesByElement(@NotNull PsiElement element, @NotNull ProcessingContext context) {
final PsiElement parent = element.getParent();
if (parent instanceof XmlProcessingInstruction) {
final String instructionTarget = JavaFxPsiUtil.getInstructionTarget("import", (XmlProcessingInstruction)parent);
if (instructionTarget != null && instructionTarget.equals(element.getText())) {
final PsiReference[] references = FxmlReferencesContributor.CLASS_REFERENCE_PROVIDER.getReferencesByString(instructionTarget, element, 0);
if (instructionTarget.endsWith(".*")) {
return ArrayUtil.remove(references, references.length - 1);
} else {
return references;
}
}
}
return PsiReference.EMPTY_ARRAY;
}
}
@@ -16,12 +16,13 @@
package org.jetbrains.plugins.javaFX.fxml.refs;
import com.intellij.codeInsight.daemon.impl.quickfix.ImportClassFixBase;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiMember;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.xml.XmlFile;
import com.intellij.psi.xml.XmlTag;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.javaFX.fxml.JavaFxPsiUtil;
/**
* User: anna
@@ -37,12 +38,25 @@ abstract class JavaFxImportClassFix extends ImportClassFixBase<XmlTag, JavaFxTag
@Nullable
@Override
protected String getReferenceName(@NotNull JavaFxTagNameReference reference) {
return getTagElement(reference).getName();
final XmlTag tagElement = getTagElement(reference);
return tagElement != null ? tagElement.getName() : null;
}
@Override
protected PsiElement getReferenceNameElement(@NotNull JavaFxTagNameReference reference) {
return getTagElement(reference).getNavigationElement();
final XmlTag tagElement = getTagElement(reference);
return tagElement != null ? tagElement.getNavigationElement() : null;
}
@Override
protected void bindReference(PsiReference reference, PsiClass targetClass) {
final PsiFile file = reference.getElement().getContainingFile();
super.bindReference(reference, targetClass);
final String qualifiedName = targetClass.getQualifiedName();
if (qualifiedName != null) {
final String shortName = StringUtil.getShortName(qualifiedName);
JavaFxPsiUtil.insertImportWhenNeeded((XmlFile)file, shortName, qualifiedName);
}
}
@Override
@@ -10,7 +10,6 @@ import com.intellij.lang.ASTNode;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElement;
import com.intellij.psi.impl.source.PostprocessReformattingAspect;
import com.intellij.psi.impl.source.xml.TagNameReference;
import com.intellij.psi.xml.*;
import com.intellij.util.Function;
@@ -38,8 +37,9 @@ public class JavaFxTagNameReference extends TagNameReference{
if (element instanceof PsiClass) {
final String qualifiedName = ((PsiClass)element).getQualifiedName();
if (qualifiedName != null) {
insertImportWhenNeeded((XmlFile)getElement().getContainingFile(), StringUtil.getShortName(qualifiedName), qualifiedName);
return getElement();
final String shortName = StringUtil.getShortName(qualifiedName);
final XmlTag tagElement = getTagElement();
return tagElement != null ? tagElement.setName(shortName) : getElement();
}
}
return super.bindToElement(element);
@@ -64,25 +64,6 @@ public class JavaFxTagNameReference extends TagNameReference{
return elements.toArray(new LookupElement[elements.size()]);
}
private static void insertImportWhenNeeded(XmlFile xmlFile,
String shortName,
String qualifiedName) {
if (shortName != null && JavaFxClassBackedElementDescriptor.findPsiClass(shortName, xmlFile.getRootTag()) == null) {
final XmlDocument document = xmlFile.getDocument();
if (document != null) {
final XmlProcessingInstruction processingInstruction = JavaFxPsiUtil
.createSingleImportInstruction(qualifiedName, xmlFile.getProject());
final XmlProlog prolog = document.getProlog();
if (prolog != null) {
prolog.add(processingInstruction);
} else {
document.addBefore(processingInstruction, document.getRootTag());
}
PostprocessReformattingAspect.getInstance(xmlFile.getProject()).doPostponedFormatting(xmlFile.getViewProvider());
}
}
}
public static class JavaFxUnresolvedTagRefsProvider extends UnresolvedReferenceQuickFixProvider<JavaFxTagNameReference> {
@Override
public void registerFixes(JavaFxTagNameReference ref, QuickFixActionRegistrar registrar) {
@@ -111,7 +92,7 @@ public class JavaFxTagNameReference extends TagNameReference{
if (object instanceof JavaFxClassBackedElementDescriptor) {
final XmlFile xmlFile = (XmlFile)context.getFile();
final String shortName = ((JavaFxClassBackedElementDescriptor)object).getName();
insertImportWhenNeeded(xmlFile, shortName, ((JavaFxClassBackedElementDescriptor)object).getQualifiedName());
JavaFxPsiUtil.insertImportWhenNeeded(xmlFile, shortName, ((JavaFxClassBackedElementDescriptor)object).getQualifiedName());
context.commitDocument();
}
}
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.layout.<error descr="Cannot resolve class 'GridPane1'">GridPane1</error>?>
<?import javafx.scene.layout.GridPane?>
<GridPane fx:id="gp"
xmlns:fx="http://javafx.com/fxml" alignment="center" hgap="10" vgap="10">
</GridPane>
@@ -0,0 +1,2 @@
import javafx.scene.control.*;
class CustomComponent extends Button {}
@@ -0,0 +1,4 @@
<?import javafx.scene.layout.GridPane?>
<?import Custom<caret>Component?>
<GridPane>
</GridPane>
@@ -0,0 +1,4 @@
<?import javafx.scene.layout.GridPane?>
<?import CustomComponent1?>
<GridPane>
</GridPane>
@@ -45,7 +45,11 @@ public class JavaFXHighlightingTest extends DaemonAnalyzerTestCase {
public void testDefaultTagProperties() throws Exception {
doTest();
}
public void testUnresolvedImport() throws Exception {
doTest();
}
public void testImageIcon() throws Exception {
configureByFiles(null, getTestName(true) + ".fxml", "appIcon.png");
doDoTest(false, false);
@@ -0,0 +1,54 @@
/*
* 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.fxml;
import com.intellij.codeInsight.TargetElementUtilBase;
import com.intellij.codeInsight.daemon.DaemonAnalyzerTestCase;
import com.intellij.codeInsight.daemon.impl.HighlightInfo;
import com.intellij.openapi.application.PluginPathManager;
import com.intellij.psi.PsiElement;
import com.intellij.refactoring.rename.RenameProcessor;
import com.intellij.testFramework.PsiTestUtil;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public class JavaFXRenameTest extends DaemonAnalyzerTestCase {
@Override
protected void setUpModule() {
super.setUpModule();
PsiTestUtil.addLibrary(getModule(), "javafx", PluginPathManager.getPluginHomePath("javaFX") + "/testData", "jfxrt.jar");
}
public void testCustomComponent() throws Exception {
doTest(getTestName(false) + "1");
}
private void doTest(final String newName) throws Exception {
configureByFiles(null, getTestName(true) + ".fxml", getTestName(false) + ".java");
PsiElement element = TargetElementUtilBase
.findTargetElement(myEditor, TargetElementUtilBase.ELEMENT_NAME_ACCEPTED | TargetElementUtilBase.REFERENCED_ELEMENT_ACCEPTED);
assertNotNull(element);
new RenameProcessor(getProject(), element, newName, true, true).run();
checkResultByFile(getTestName(true) + "_after.fxml");
}
@NotNull
@Override
protected String getTestDataPath() {
return PluginPathManager.getPluginHomePath("javaFX") + "/testData/rename/";
}
}
@@ -157,6 +157,8 @@ public class XmlHighlightVisitor extends XmlElementVisitor implements HighlightV
}
}
}
} else if (tokenType == XmlTokenType.XML_TAG_CHARACTERS && token.getParent() instanceof XmlProcessingInstruction) {
checkReferences(token);
}
}
@@ -69,6 +69,9 @@ public class XmlFilterLexer extends BaseFilterLexer {
else if (tokenType == XmlElementType.XML_TEXT) {
scanWordsInToken(UsageSearchContext.IN_PLAIN_TEXT | UsageSearchContext.IN_FOREIGN_LANGUAGES, false, false);
}
else if (tokenType == XmlTokenType.XML_TAG_CHARACTERS) {
scanWordsInToken(UsageSearchContext.IN_PLAIN_TEXT | UsageSearchContext.IN_FOREIGN_LANGUAGES, false, false);
}
else if (!ourNoWordsTokenSet.contains(tokenType)) {
scanWordsInToken(UsageSearchContext.IN_PLAIN_TEXT, false, false);
}
@@ -23,6 +23,7 @@ import com.intellij.psi.impl.source.tree.LeafPsiElement;
import com.intellij.psi.search.PsiElementProcessor;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.tree.xml.IDTDElementType;
import com.intellij.psi.xml.XmlProcessingInstruction;
import com.intellij.psi.xml.XmlToken;
import com.intellij.psi.xml.XmlTokenType;
import org.jetbrains.annotations.NotNull;
@@ -66,7 +67,8 @@ public class XmlTokenImpl extends LeafPsiElement implements XmlToken, Navigatabl
final IElementType elementType = getElementType();
if (elementType == XmlTokenType.XML_DATA_CHARACTERS ||
elementType == XmlTokenType.XML_CHAR_ENTITY_REF
elementType == XmlTokenType.XML_CHAR_ENTITY_REF ||
(elementType == XmlTokenType.XML_TAG_CHARACTERS && getParent() instanceof XmlProcessingInstruction)
) {
return ReferenceProvidersRegistry.getReferencesFromProviders(this, XmlToken.class);
} else if (elementType == XmlTokenType.XML_NAME && getParent() instanceof PsiErrorElement) {