diff --git a/plugins/javaFX/src/org/jetbrains/plugins/javaFX/fxml/JavaFxCommonClassNames.java b/plugins/javaFX/src/org/jetbrains/plugins/javaFX/fxml/JavaFxCommonClassNames.java index f9d8baaeec14..80d73846fbf1 100644 --- a/plugins/javaFX/src/org/jetbrains/plugins/javaFX/fxml/JavaFxCommonClassNames.java +++ b/plugins/javaFX/src/org/jetbrains/plugins/javaFX/fxml/JavaFxCommonClassNames.java @@ -22,7 +22,7 @@ import org.jetbrains.annotations.NonNls; * Date: 1/16/13 */ public class JavaFxCommonClassNames { - @NonNls public static final String JAVAFX_BEANS_PROPERTY_PROPERTY = "javafx.beans.property.Property"; + @NonNls public static final String JAVAFX_BEANS_PROPERTY = "javafx.beans.property.Property"; @NonNls public static final String JAVAFX_ANCHOR_PANE = "javafx.scene.layout.AnchorPane"; @NonNls public static final String JAVAFX_EVENT = "javafx.event.Event"; @NonNls public static final String JAVAFX_BEANS_DEFAULT_PROPERTY = "javafx.beans.DefaultProperty"; diff --git a/plugins/javaFX/src/org/jetbrains/plugins/javaFX/fxml/JavaFxPsiUtil.java b/plugins/javaFX/src/org/jetbrains/plugins/javaFX/fxml/JavaFxPsiUtil.java index 2c3059cb0e8e..f619e1504280 100644 --- a/plugins/javaFX/src/org/jetbrains/plugins/javaFX/fxml/JavaFxPsiUtil.java +++ b/plugins/javaFX/src/org/jetbrains/plugins/javaFX/fxml/JavaFxPsiUtil.java @@ -296,4 +296,27 @@ public class JavaFxPsiUtil { return false; } + + public static boolean isExpressionBinding(String value) { + if (!value.startsWith("$")) return false; + value = value.substring(1); + return value.startsWith("{") && value.endsWith("}") && value.contains("."); + } + + @Nullable + public static PsiType getPropertyType(final PsiType type, final Project project) { + final PsiClass psiClass = PsiUtil.resolveClassInType(type); + if (psiClass != null) { + final PsiClass propertyClass = JavaPsiFacade.getInstance(project).findClass(JavaFxCommonClassNames.JAVAFX_BEANS_PROPERTY, + GlobalSearchScope.allScope(project)); + if (propertyClass != null) { + final PsiSubstitutor substitutor = + TypeConversionUtil.getClassSubstitutor(propertyClass, psiClass, PsiSubstitutor.EMPTY); + if (substitutor != null) { + return substitutor.substitute(propertyClass.getTypeParameters()[0]); + } + } + } + return null; + } } diff --git a/plugins/javaFX/src/org/jetbrains/plugins/javaFX/fxml/descriptors/JavaFxClassBackedElementDescriptor.java b/plugins/javaFX/src/org/jetbrains/plugins/javaFX/fxml/descriptors/JavaFxClassBackedElementDescriptor.java index 5897911a512c..b1f522bc1a06 100644 --- a/plugins/javaFX/src/org/jetbrains/plugins/javaFX/fxml/descriptors/JavaFxClassBackedElementDescriptor.java +++ b/plugins/javaFX/src/org/jetbrains/plugins/javaFX/fxml/descriptors/JavaFxClassBackedElementDescriptor.java @@ -195,7 +195,7 @@ public class JavaFxClassBackedElementDescriptor implements XmlElementDescriptor, if (field.hasModifierProperty(PsiModifier.STATIC)) continue; final PsiType fieldType = field.getType(); if (!JavaFxPsiUtil.isReadOnly(field.getName(), context) && - InheritanceUtil.isInheritor(fieldType, JavaFxCommonClassNames.JAVAFX_BEANS_PROPERTY_PROPERTY) || + InheritanceUtil.isInheritor(fieldType, JavaFxCommonClassNames.JAVAFX_BEANS_PROPERTY) || fieldType.equalsToText(CommonClassNames.JAVA_LANG_STRING) || includeListProperties && GenericsHighlightUtil.getCollectionItemType(field.getType(), myPsiClass.getResolveScope()) != null) { children.add(factory.fun(field)); diff --git a/plugins/javaFX/src/org/jetbrains/plugins/javaFX/fxml/refs/JavaFxAnnotator.java b/plugins/javaFX/src/org/jetbrains/plugins/javaFX/fxml/refs/JavaFxAnnotator.java index 5cdf799d8ea3..867c6d760df7 100644 --- a/plugins/javaFX/src/org/jetbrains/plugins/javaFX/fxml/refs/JavaFxAnnotator.java +++ b/plugins/javaFX/src/org/jetbrains/plugins/javaFX/fxml/refs/JavaFxAnnotator.java @@ -59,15 +59,17 @@ public class JavaFxAnnotator implements Annotator { if (!JavaFxFileTypeFactory.isFxml(containingFile)) return; if (element instanceof XmlAttributeValue) { final PsiReference[] references = element.getReferences(); - for (PsiReference reference : references) { - final PsiElement resolve = reference.resolve(); - if (resolve instanceof PsiMember) { - if (!JavaFxPsiUtil.isVisibleInFxml((PsiMember)resolve)) { - final String symbolPresentation = "'" + SymbolPresentationUtil.getSymbolPresentableText(resolve) + "'"; - final Annotation annotation = holder.createErrorAnnotation(element, - symbolPresentation + (resolve instanceof PsiClass ? " should be public" : " should be public or annotated with @FXML")); - if (!(resolve instanceof PsiClass)) { - annotation.registerUniversalFix(new AddAnnotationFix(JavaFxCommonClassNames.JAVAFX_FXML_ANNOTATION, (PsiMember)resolve, ArrayUtil.EMPTY_STRING_ARRAY), null, null); + if (!JavaFxPsiUtil.isExpressionBinding(((XmlAttributeValue)element).getValue())) { + for (PsiReference reference : references) { + final PsiElement resolve = reference.resolve(); + if (resolve instanceof PsiMember) { + if (!JavaFxPsiUtil.isVisibleInFxml((PsiMember)resolve)) { + final String symbolPresentation = "'" + SymbolPresentationUtil.getSymbolPresentableText(resolve) + "'"; + final Annotation annotation = holder.createErrorAnnotation(element, + symbolPresentation + (resolve instanceof PsiClass ? " should be public" : " should be public or annotated with @FXML")); + if (!(resolve instanceof PsiClass)) { + annotation.registerUniversalFix(new AddAnnotationFix(JavaFxCommonClassNames.JAVAFX_FXML_ANNOTATION, (PsiMember)resolve, ArrayUtil.EMPTY_STRING_ARRAY), null, null); + } } } } diff --git a/plugins/javaFX/src/org/jetbrains/plugins/javaFX/fxml/refs/JavaFxComponentIdReferenceProvider.java b/plugins/javaFX/src/org/jetbrains/plugins/javaFX/fxml/refs/JavaFxComponentIdReferenceProvider.java index 343dcf9528df..cbd84f2adf28 100644 --- a/plugins/javaFX/src/org/jetbrains/plugins/javaFX/fxml/refs/JavaFxComponentIdReferenceProvider.java +++ b/plugins/javaFX/src/org/jetbrains/plugins/javaFX/fxml/refs/JavaFxComponentIdReferenceProvider.java @@ -16,18 +16,24 @@ package org.jetbrains.plugins.javaFX.fxml.refs; import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.*; import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.util.TypeConversionUtil; import com.intellij.psi.xml.XmlAttribute; import com.intellij.psi.xml.XmlAttributeValue; import com.intellij.psi.xml.XmlTag; import com.intellij.util.ArrayUtil; import com.intellij.util.ProcessingContext; +import com.intellij.xml.XmlAttributeDescriptor; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.javaFX.fxml.FxmlConstants; +import org.jetbrains.plugins.javaFX.fxml.JavaFxPsiUtil; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; /** @@ -57,24 +63,105 @@ class JavaFxComponentIdReferenceProvider extends PsiReferenceProvider { } }); - return new PsiReference[]{new PsiReferenceBase(xmlAttributeValue) { - @Override - public TextRange getRangeInElement() { - final TextRange rangeInElement = super.getRangeInElement(); - return startsWithDollar ? new TextRange(rangeInElement.getStartOffset() + 1, rangeInElement.getEndOffset()) : rangeInElement; - } + if (JavaFxPsiUtil.isExpressionBinding(value)) { + final String expressionText = referencesId.substring(1, referencesId.length() - 1); + final String newId = StringUtil.getPackageName(expressionText); + final String fieldRef = StringUtil.getShortName(expressionText); - @Nullable - @Override - public PsiElement resolve() { - return fileIds.get(referencesId); + final JavaFxIdReferenceBase idReferenceBase = new JavaFxIdReferenceBase(xmlAttributeValue, fileIds, newId); + final TextRange range = idReferenceBase.getRangeInElement(); + final int startOffset = range.getStartOffset() + 2; + final int endOffset = startOffset + newId.length(); + idReferenceBase.setRangeInElement(new TextRange(startOffset, endOffset)); + if (fileIds.containsKey(newId)) { + final XmlAttributeValue attributeValue = fileIds.get(newId); + final PsiClass tagClass = JavaFxPsiUtil.getTagClass(attributeValue); + if (tagClass != null) { + final JavaFxExpressionReferenceBase referenceBase = new JavaFxExpressionReferenceBase(xmlAttributeValue, tagClass, fieldRef); + final TextRange textRange = referenceBase.getRangeInElement(); + referenceBase.setRangeInElement(new TextRange(endOffset + 1, textRange.getEndOffset() - 1)); + return new PsiReference[] {idReferenceBase, referenceBase}; + } } + return new PsiReference[] {idReferenceBase}; + } + final JavaFxIdReferenceBase idReferenceBase = new JavaFxIdReferenceBase(xmlAttributeValue, fileIds, referencesId); + if (startsWithDollar) { + final TextRange rangeInElement = idReferenceBase.getRangeInElement(); + idReferenceBase.setRangeInElement(new TextRange(rangeInElement.getStartOffset() + 1, rangeInElement.getEndOffset())); + } + return new PsiReference[]{idReferenceBase}; + } - @NotNull - @Override - public Object[] getVariants() { - return ArrayUtil.toStringArray(fileIds.keySet()); + private static class JavaFxIdReferenceBase extends PsiReferenceBase { + private final Map myFileIds; + private final String myReferencesId; + + public JavaFxIdReferenceBase(XmlAttributeValue xmlAttributeValue, + Map fileIds, + String referencesId) { + super(xmlAttributeValue); + myFileIds = fileIds; + myReferencesId = referencesId; + } + + @Nullable + @Override + public PsiElement resolve() { + return myFileIds.get(myReferencesId); + } + + @NotNull + @Override + public Object[] getVariants() { + return ArrayUtil.toStringArray(myFileIds.keySet()); + } + } + + private static class JavaFxExpressionReferenceBase extends PsiReferenceBase { + private final PsiClass myTagClass; + private final String myFieldName; + + public JavaFxExpressionReferenceBase(XmlAttributeValue xmlAttributeValue, PsiClass tagClass, String fieldName) { + super(xmlAttributeValue); + myTagClass = tagClass; + myFieldName = fieldName; + } + + @Nullable + @Override + public PsiElement resolve() { + return myTagClass.findFieldByName(myFieldName, true); + } + + @NotNull + @Override + public Object[] getVariants() { + final PsiElement parent = getElement().getParent(); + if (parent instanceof XmlAttribute) { + final XmlAttributeDescriptor descriptor = ((XmlAttribute)parent).getDescriptor(); + if (descriptor != null) { + final PsiElement declaration = descriptor.getDeclaration(); + if (declaration instanceof PsiField) { + return collectProperties((PsiField)declaration); + } + } } - }}; + return ArrayUtil.EMPTY_OBJECT_ARRAY; + } + + private Object[] collectProperties(@NotNull PsiField psiField) { + final PsiType type = psiField.getType(); + final PsiType propertyType = JavaFxPsiUtil.getPropertyType(type, psiField.getProject()); + final List objs = new ArrayList(); + for (PsiField field : myTagClass.getAllFields()) { + if (field.hasModifierProperty(PsiModifier.STATIC)) continue; + final PsiType fieldType = field.getType(); + if (TypeConversionUtil.isAssignable(type, fieldType) || (propertyType != null && TypeConversionUtil.isAssignable(propertyType, fieldType))) { + objs.add(field); + } + } + return ArrayUtil.toObjectArray(objs); + } } } diff --git a/plugins/javaFX/testData/completion/expressionBinding.fxml b/plugins/javaFX/testData/completion/expressionBinding.fxml new file mode 100644 index 000000000000..91b390e5bc68 --- /dev/null +++ b/plugins/javaFX/testData/completion/expressionBinding.fxml @@ -0,0 +1,8 @@ + + + + + + + diff --git a/plugins/javaFX/testData/completion/expressionBinding_after.fxml b/plugins/javaFX/testData/completion/expressionBinding_after.fxml new file mode 100644 index 000000000000..448195701a3b --- /dev/null +++ b/plugins/javaFX/testData/completion/expressionBinding_after.fxml @@ -0,0 +1,8 @@ + + + + + + + diff --git a/plugins/javaFX/testData/highlighting/expressionBinding.fxml b/plugins/javaFX/testData/highlighting/expressionBinding.fxml new file mode 100644 index 000000000000..448195701a3b --- /dev/null +++ b/plugins/javaFX/testData/highlighting/expressionBinding.fxml @@ -0,0 +1,8 @@ + + + + + + + diff --git a/plugins/javaFX/testSrc/org/jetbrains/plugins/javaFX/fxml/JavaFXHighlightingTest.java b/plugins/javaFX/testSrc/org/jetbrains/plugins/javaFX/fxml/JavaFXHighlightingTest.java index 3820d5d64ce1..f57a0ce5ed6c 100644 --- a/plugins/javaFX/testSrc/org/jetbrains/plugins/javaFX/fxml/JavaFXHighlightingTest.java +++ b/plugins/javaFX/testSrc/org/jetbrains/plugins/javaFX/fxml/JavaFXHighlightingTest.java @@ -184,6 +184,10 @@ public class JavaFXHighlightingTest extends DaemonAnalyzerTestCase { doDoTest(false, false); } + public void testExpressionBinding() throws Exception { + doTest(); + } + @NotNull @Override protected String getTestDataPath() { diff --git a/plugins/javaFX/testSrc/org/jetbrains/plugins/javaFX/fxml/JavaFxCompletionTest.java b/plugins/javaFX/testSrc/org/jetbrains/plugins/javaFX/fxml/JavaFxCompletionTest.java index cf22a32fa911..ce18d272cd7a 100644 --- a/plugins/javaFX/testSrc/org/jetbrains/plugins/javaFX/fxml/JavaFxCompletionTest.java +++ b/plugins/javaFX/testSrc/org/jetbrains/plugins/javaFX/fxml/JavaFxCompletionTest.java @@ -93,6 +93,10 @@ public class JavaFxCompletionTest extends CompletionTestCase { doTest(); } + public void testExpressionBinding() throws Exception { + doTest("text"); + } + public void testReadOnly() throws Exception { configureByFile(getTestName(true) + ".fxml"); assertTrue(myItems.length > 0);